Related
I use a Proxy object with the idea that whenever a property gets updated, some other side-effect can also be initiated. I don't want to initiate the side effects in the many places that properties get set (DRY principle).
Somewhat contrived example code:
const session = new Proxy(
{ id: undefined as number, age: undefined as number}, // =target
{
set: (target, property, value): boolean => {
switch (property) {
case 'id': {
target[property] = value;
this.notifyIdWasUpdated(value);
return true;
}
case 'age': {
target[property] = value;
this.updateAgeDisplay(value);
return true;
}
default: {
return false;
}
}
}
}
);
My problem is that when I use my IDE's refactoring to change a property name (key) of the target object (e.g. age), the string constants in the case statements (e.g. 'age') don't get updated as well (potentially causing a bug).
Question: Is there a way to dynamically get a string value 'key' from an expression obj.key in the case statement (which would then be refactoring proof)? (Sort of the inverse of the ['key'] accessor, in a way...) Alternatively, can you suggest another way to structure the above code to guard against this sort of programmer oversight?
I have found Get object property name as a string, but wonder if there is a less "iffy" solution - IMHO the tradeoff between a potential problem and adding a lot of code to guard against it is not worth it. (Many techniques seem to iterate through all keys and match on either property type or value; these will not be safe enough.)
Typescript's documentation seems to say that metadata emission for reflection-like use is not yet officially adopted. Also not worth it IMHO to add a whole experimental library just for this.
You can try to use keyof here.
interface Session {
id: number
age: number
}
const session1 = new Proxy(
{ id: 0, age: 0 } as Session,
{
set: (target, property: keyof Session, value): boolean => {
switch (property) {
case 'id': {
target[property] = value;
this.notifyIdWasUpdated(value);
return true;
}
case 'age': {
target[property] = value;
this.updateAgeDisplay(value);
return true;
}
default: {
return false;
}
}
}
}
);
This will not be renamed automatically, but typescript will show error if property in case doesn't exist in Session.
The following case should allow automatic rename:
interface Session {
id: number
age: number
}
type Handlers<Model> = {
[Key in keyof Model]: (newValue: Model[Key]) => void;
}
// Partial<Handlers<Session>> in case you don't want to handle each property
const handlers: Handlers<Session> = {
id: () => { },
age: () => { },
}
const session = new Proxy(
{ id: 0, age: 0 } as Session,
{
set: (target, property: keyof Session, value): boolean => {
const handler = handlers[property];
if (handler) {
handler(value)
return true;
}
return false;
}
}
);
The simples solution would be something like this:
function nameof<TType>(selector: (t?: TType) => any) {
const match = selector
.toString()
.match(/=>\s*(?:[a-zA-Z0-9_$]+\.?)*?([a-zA-Z0-9_$]+)$/);
if (match) {
return match[1];
}
return undefined;
}
interface MyType {
id: any;
age: number;
}
const session = new Proxy(
{ id: undefined as number, age: undefined as number }, // =target
{
set: (target, property, value): boolean => {
switch (property) {
case nameof<MyType>((t) => t.id): {
target[property] = value;
this.notifyIdWasUpdated(value);
return true;
}
case nameof<MyType>((t) => t.age): {
target[property] = value;
this.updateAgeDisplay(value);
return true;
}
default: {
return false;
}
}
},
}
);
DEMO
NOTE: Careful, if you target ES5! Arrow function is transpiled into regular function with return so that regex will not work, you have to change the regex.
Although another answer was chosen, the problem with the given sample code is on a somewhat higher level of abstraction. Since the session object encapsulates a number of properties, the session object should be handled as a unit and not the properties separately. (There is probably a code smell name or some other warning against this...)
The sample would then simply be:
session = new Proxy(
{ id: undefined, age: undefined}, // =target
{
set: (target, property, value): boolean => {
if (typeof property === 'string' && Object.keys(target).includes(<string>property)) {
target[property] = value;
doSideEffects(target);
return true;
} else {
return false;
}
},
}
);
This simplifies the handler in the Proxy.
(I'm the OP. In my case, it has now also simplified the side effect code considerably. I guess the rubber duck effect came into play...)
The Problem:
I have this function. Which removes all KeyValue Pairs that have an Empty String as value from a Payload.
The problem is, that I want to apply it in an Object that is Nested. Not the whole Payload Object. Example:
configuration: {
conf1: "",
conf2: "Something",
conf3: ""
},
resourceName: "Name"
In this case I want to apply my UtilityFunction, in the configurationObject. Which would result in this:
configuration: {
conf2: "Something",
},
resourceName: "Name"
So, I used a few Methods. Object.assign, rest, in order to supply an object with all the outside parameters, but also, the output of the utility applied to just the configuration object.
I tried:
Object.assign(formValues, removeEmptyKeysUtil(formValues.configuration));
// Results in printing the values in the main object.
Also:
{ formValues, ...removeEmptyKeysUtil(formValues.configuration) };
// which does not do anything
Can you please help me, and explain what am I doing wrong?
The stripEmptyStrings function takes the original object and the target key.
this function can also handle if the target property of the object is "" and will delete that property regardless of if it is an Object or not.
const stripEmptyStrings = (object, target) => {
const _target = object[target];
if (_target === "") {
delete object[target]
return object;
}
else if (typeof _target === "object") {
Object.keys(_target).forEach((k) => {
if (_target[k] === "") delete _target[k];
})
}
return {
...object,
[target]: _target,
}
}
const obj1 = {
configuration: {
conf1: "",
conf2: "Something",
conf3: ""
},
resourceName: "Name",
}
const result1 = stripEmptyStrings(obj1, "configuration");
console.log(result1)
const obj2 = {
removeMe: "",
resourceName: "Name2",
}
const result2 = stripEmptyStrings(obj2, "removeMe");
console.log(result2)
I'm using the following code to get object from react state.
const { organizations } = this.state;
The sate object is as following.
this.state = {
userOrganizations: {},
OrganizationUsers: {}
}
userOrganizations is actually an object with an inner object named organizations. How can I map this using es6 code?
Edit
What I actually need is to get inner objects of both the userOrganizations and OrganizationUsers using the following code.
const { organizations, users } = this.state;
The organizations and users are sub objects which are inside the userOrganizations and OrganizationUsers.
So when I want to handle them, will they work with just calling
const { organizations, users } = this.state.userOrganizations, this.state.OrganizationUsers;
You can nest destructruring like
const { userOrganizations : { organizations } } = this.state;
or simply write
const { organizations } = this.state.userOrganizations;
This is so simple but still many got it wrong. Here is an example of inner destructuring
const obj = {
someArray: [ 1, 2, 3],
someInnerObj : {num: 123, txt: 'text'}
}
const {someArray: [first,second], someInnerObj: { num: myNum, txt: meText}} = obj
console.log(first,second,myNum,meText)
Try it in console
Just use dot notation until you get to the parent object just above the property you want
const obj = { outer: { inner: 'value' }};
const { inner } = obj.outer;
console.log(inner);
To destructure more than one thing at a time in different nested levels, try something like:
const x = {
state: {
userOrganizations: {
organizations: 'orgValue'
},
OrganizationUsers: {
users: 'userValue'
}
}
}
const { userOrganizations: { organizations }, OrganizationUsers: { users } } = x.state;
console.log(organizations + ' ' + users);
If i'am not wrong this should solve your issue,
const { organizations } = this.state.userOrganizations;
What's a good and short way to remove a value from an object at a specific key without mutating the original object?
I'd like to do something like:
let o = {firstname: 'Jane', lastname: 'Doe'};
let o2 = doSomething(o, 'lastname');
console.log(o.lastname); // 'Doe'
console.log(o2.lastname); // undefined
I know there are a lot of immutability libraries for such tasks, but I'd like to get away without a library. But to do this, a requirement would be to have an easy and short way that can be used throughout the code, without abstracting the method away as a utility function.
E.g. for adding a value I do the following:
let o2 = {...o1, age: 31};
This is quite short, easy to remember and doesn't need a utility function.
Is there something like this for removing a value? ES6 is very welcome.
Thank you very much!
Update:
You could remove a property from an object with a tricky Destructuring assignment:
const doSomething = (obj, prop) => {
let {[prop]: omit, ...res} = obj
return res
}
Though, if property name you want to remove is static, then you could remove it with a simple one-liner:
let {lastname, ...o2} = o
The easiest way is simply to Or you could clone your object before mutating it:
const doSomething = (obj, prop) => {
let res = Object.assign({}, obj)
delete res[prop]
return res
}
Alternatively you could use omit function from lodash utility library:
let o2 = _.omit(o, 'lastname')
It's available as a part of lodash package, or as a standalone lodash.omit package.
With ES7 object destructuring:
const myObject = {
a: 1,
b: 2,
c: 3
};
const { a, ...noA } = myObject;
console.log(noA); // => { b: 2, c: 3 }
one line solution
const removeKey = (key, {[key]: _, ...rest}) => rest;
Explanations:
This is a generic arrow function to remove a specific key. The first argument is the name of the key to remove, the second is the object from where you want to remove the key. Note that by restructuring it, we generate the curated result, then return it.
Example:
let example = {
first:"frefrze",
second:"gergerge",
third: "gfgfg"
}
console.log(removeKey('third', example))
/*
Object {
first: "frefrze",
second: "gergerge"
}
*/
To add some spice bringing in Performance. Check this thread bellow
https://github.com/googleapis/google-api-nodejs-client/issues/375
The use of the delete operator has performance negative effects for
the V8 hidden classes pattern. In general it's recommended do not use
it.
Alternatively, to remove object own enumerable properties, we could
create a new object copy without those properties (example using
lodash):
_.omit(o, 'prop', 'prop2')
Or even define the property value to null or undefined (which is
implicitly ignored when serializing to JSON):
o.prop = undefined
You can use too the destructing way
const {remov1, remov2, ...new} = old;
old = new;
And a more practical exmple:
this._volumes[this._minCandle] = undefined;
{
const {[this._minCandle]: remove, ...rest} = this._volumes;
this._volumes = rest;
}
As you can see you can use [somePropsVarForDynamicName]: scopeVarName syntax for dynamic names. And you can put all in brackets (new block) so the rest will be garbage collected after it.
Here a test:
exec:
Or we can go with some function like
function deleteProps(obj, props) {
if (!Array.isArray(props)) props = [props];
return Object.keys(obj).reduce((newObj, prop) => {
if (!props.includes(prop)) {
newObj[prop] = obj[prop];
}
return newObj;
}, {});
}
for typescript
function deleteProps(obj: Object, props: string[]) {
if (!Array.isArray(props)) props = [props];
return Object.keys(obj).reduce((newObj, prop) => {
if (!props.includes(prop)) {
newObj[prop] = obj[prop];
}
return newObj;
}, {});
}
Usage:
let a = {propH: 'hi', propB: 'bye', propO: 'ok'};
a = deleteProps(a, 'propB');
// or
a = deleteProps(a, ['propB', 'propO']);
This way a new object is created. And the fast property of the object is kept. Which can be important or matter. If the mapping and the object will be accessed many many times.
Also associating undefined can be a good way to go with. When you can afford it. And for the keys you can too check the value. For instance to get all the active keys you do something like:
const allActiveKeys = Object.keys(myObj).filter(k => myObj[k] !== undefined);
//or
const allActiveKeys = Object.keys(myObj).filter(k => myObj[k]); // if any false evaluated value is to be stripped.
Undefined is not suited though for big list. Or development over time with many props to come in. As the memory usage will keep growing and will never get cleaned. So it depend on the usage. And just creating a new object seem to be the good way.
Then the Premature optimization is the root of all evil will kick in. So you need to be aware of the trade off. And what is needed and what's not.
Note about _.omit() from lodash
It's removed from version 5. You can't find it in the repo. And here an issue that talk about it.
https://github.com/lodash/lodash/issues/2930
v8
You can check this which is a good reading https://v8.dev/blog/fast-properties
As suggested in the comments above if you want to extend this to remove more than one item from your object I like to use filter. and reduce
eg
const o = {
"firstname": "Jane",
"lastname": "Doe",
"middlename": "Kate",
"age": 23,
"_id": "599ad9f8ebe5183011f70835",
"index": 0,
"guid": "1dbb6a4e-f82d-4e32-bb4c-15ed783c70ca",
"isActive": true,
"balance": "$1,510.89",
"picture": "http://placehold.it/32x32",
"eyeColor": "green",
"registered": "2014-08-17T09:21:18 -10:00",
"tags": [
"consequat",
"ut",
"qui",
"nulla",
"do",
"sunt",
"anim"
]
};
const removeItems = ['balance', 'picture', 'tags']
console.log(formatObj(o, removeItems))
function formatObj(obj, removeItems) {
return {
...Object.keys(obj)
.filter(item => !isInArray(item, removeItems))
.reduce((newObj, item) => {
return {
...newObj, [item]: obj[item]
}
}, {})
}
}
function isInArray(value, array) {
return array.indexOf(value) > -1;
}
My issue with the accepted answer, from an ESLint rule standard, if you try to destructure:
const { notNeeded, alsoNotNeeded, ...rest } = { ...ogObject };
the 2 new variables, notNeeded and alsoNotNeeded may throw a warning or error depending on your setup since they are now unused. So why create new vars if unused?
I think you need to use the delete function truly.
export function deleteKeyFromObject(obj, key) {
return Object.fromEntries(Object.entries(obj).filter(el => el[0] !== key))
}
with lodash cloneDeep and delete
(note: lodash clone can be used instead for shallow objects)
const obj = {a: 1, b: 2, c: 3}
const unwantedKey = 'a'
const _ = require('lodash')
const objCopy = _.cloneDeep(obj)
delete objCopy[unwantedKey]
// objCopy = {b: 2, c: 3}
For my code I wanted a short version for the return value of map() but the multiline/mutli operations solutions were "ugly". The key feature is the old void(0) which resolve to undefined.
let o2 = {...o, age: 31, lastname: void(0)};
The property stays in the object:
console.log(o2) // {firstname: "Jane", lastname: undefined, age: 31}
but the transmit framework kills it for me (b.c. stringify):
console.log(JSON.stringify(o2)) // {"firstname":"Jane","age":31}
I wrote big function about issue for me. The function clear all values of props (not itself, only value), arrays etc. as multidimensional.
NOTE: The function clear elements in arrays and arrays become an empty array. Maybe this case can be added to function as optional.
https://gist.github.com/semihkeskindev/d979b169e4ee157503a76b06573ae868
function clearAllValues(data, byTypeOf = false) {
let clearValuesTypeOf = {
boolean: false,
number: 0,
string: '',
}
// clears array if data is array
if (Array.isArray(data)) {
data = [];
} else if (typeof data === 'object' && data !== null) {
// loops object if data is object
Object.keys(data).forEach((key, index) => {
// clears array if property value is array
if (Array.isArray(data[key])) {
data[key] = [];
} else if (typeof data[key] === 'object' && data !== null) {
data[key] = this.clearAllValues(data[key], byTypeOf);
} else {
// clears value by typeof value if second parameter is true
if (byTypeOf) {
data[key] = clearValuesTypeOf[typeof data[key]];
} else {
// value changes as null if second parameter is false
data[key] = null;
}
}
});
} else {
if (byTypeOf) {
data = clearValuesTypeOf[typeof data];
} else {
data = null;
}
}
return data;
}
Here is an example that clear all values without delete props
let object = {
name: 'Semih',
lastname: 'Keskin',
brothers: [
{
name: 'Melih Kayra',
age: 9,
}
],
sisters: [],
hobbies: {
cycling: true,
listeningMusic: true,
running: false,
}
}
console.log(object);
// output before changed: {"name":"Semih","lastname":"Keskin","brothers":[{"name":"Melih Kayra","age":9}],"sisters":[],"hobbies":{"cycling":true,"listeningMusic":true,"running":false}}
let clearObject = clearAllValues(object);
console.log(clearObject);
// output after changed: {"name":null,"lastname":null,"brothers":[],"sisters":[],"hobbies":{"cycling":null,"listeningMusic":null,"running":null}}
let clearObject2 = clearAllValues(object);
console.log(clearObject2);
// output after changed by typeof: {"name":"","lastname":"","brothers":[],"sisters":[],"hobbies":{"cycling":false,"listeningMusic":false,"running":false}}
I want to be able to pass any javascript object containing camelCase keys through a method and return an object with underscore_case keys, mapped to the same values.
So, I have this:
var camelCased = {firstName: 'Jon', lastName: 'Smith'}
And I want a method to output this:
{first_name: 'Jon', last_name: 'Jon'}
What's the fastest way to write a method that takes any object with any number of key/value pairs and outputs the underscore_cased version of that object?
Here's your function to convert camelCase to underscored text (see the jsfiddle):
function camelToUnderscore(key) {
return key.replace( /([A-Z])/g, "_$1").toLowerCase();
}
console.log(camelToUnderscore('helloWorldWhatsUp'));
Then you can just loop (see the other jsfiddle):
var original = {
whatsUp: 'you',
myName: 'is Bob'
},
newObject = {};
function camelToUnderscore(key) {
return key.replace( /([A-Z])/g, "_$1" ).toLowerCase();
}
for(var camel in original) {
newObject[camelToUnderscore(camel)] = original[camel];
}
console.log(newObject);
If you have an object with children objects, you can use recursion and change all properties:
function camelCaseKeysToUnderscore(obj){
if (typeof(obj) != "object") return obj;
for(var oldName in obj){
// Camel to underscore
newName = oldName.replace(/([A-Z])/g, function($1){return "_"+$1.toLowerCase();});
// Only process if names are different
if (newName != oldName) {
// Check for the old property name to avoid a ReferenceError in strict mode.
if (obj.hasOwnProperty(oldName)) {
obj[newName] = obj[oldName];
delete obj[oldName];
}
}
// Recursion
if (typeof(obj[newName]) == "object") {
obj[newName] = camelCaseKeysToUnderscore(obj[newName]);
}
}
return obj;
}
So, with an object like this:
var obj = {
userId: 20,
userName: "John",
subItem: {
paramOne: "test",
paramTwo: false
}
}
newobj = camelCaseKeysToUnderscore(obj);
You'll get:
{
user_id: 20,
user_name: "John",
sub_item: {
param_one: "test",
param_two: false
}
}
es6 node solution below. to use, require this file, then pass object you want converted into the function and it will return the camelcased / snakecased copy of the object.
const snakecase = require('lodash.snakecase');
const traverseObj = (obj) => {
const traverseArr = (arr) => {
arr.forEach((v) => {
if (v) {
if (v.constructor === Object) {
traverseObj(v);
} else if (v.constructor === Array) {
traverseArr(v);
}
}
});
};
Object.keys(obj).forEach((k) => {
if (obj[k]) {
if (obj[k].constructor === Object) {
traverseObj(obj[k]);
} else if (obj[k].constructor === Array) {
traverseArr(obj[k]);
}
}
const sck = snakecase(k);
if (sck !== k) {
obj[sck] = obj[k];
delete obj[k];
}
});
};
module.exports = (o) => {
if (!o || o.constructor !== Object) return o;
const obj = Object.assign({}, o);
traverseObj(obj);
return obj;
};
Came across this exact problem when working between JS & python/ruby objects. I noticed the accepted solution is using for in which will throw eslint error messages at you ref: https://github.com/airbnb/javascript/issues/851 which alludes to rule 11.1 re: use of pure functions rather than side effects ref:https://github.com/airbnb/javascript#iterators--nope
To that end, figured i'd share the below which passed the said rules.
import { snakeCase } from 'lodash'; // or use the regex in the accepted answer
camelCase = obj => {
const camelCaseObj = {};
for (const key of Object.keys(obj)){
if (Object.prototype.hasOwnProperty.call(obj, key)) {
camelCaseObj[snakeCase(key)] = obj[key];
}
}
return camelCaseObj;
};
Marcos Dimitrio posted above with his conversion function, which works but is not a pure function as it changes the original object passed in, which may be an undesireable side effect. Below returns a new object that doesn't modify the original.
export function camelCaseKeysToSnake(obj){
if (typeof(obj) != "object") return obj;
let newObj = {...obj}
for(var oldName in newObj){
// Camel to underscore
let newName = oldName.replace(/([A-Z])/g, function($1){return "_"+$1.toLowerCase();});
// Only process if names are different
if (newName != oldName) {
// Check for the old property name to avoid a ReferenceError in strict mode.
if (newObj.hasOwnProperty(oldName)) {
newObj[newName] = newObj[oldName];
delete newObj[oldName];
}
}
// Recursion
if (typeof(newObj[newName]) == "object") {
newObj[newName] = camelCaseKeysToSnake(newObj[newName]);
}
}
return newObj;
}
this library does exactly that: case-converter
It converts snake_case to camelCase and vice versa
const caseConverter = require('case-converter')
const snakeCase = {
an_object: {
nested_string: 'nested content',
nested_array: [{ an_object: 'something' }]
},
an_array: [
{ zero_index: 0 },
{ one_index: 1 }
]
}
const camelCase = caseConverter.toCamelCase(snakeCase);
console.log(camelCase)
/*
{
anObject: {
nestedString: 'nested content',
nestedArray: [{ anObject: 'something' }]
},
anArray: [
{ zeroIndex: 0 },
{ oneIndex: 1 }
]
}
*/
following what's suggested above, case-converter library is deprectaed, use snakecase-keys instead -
https://github.com/bendrucker/snakecase-keys
supports also nested objects & exclusions.
Any of the above snakeCase functions can be used in a reduce function as well:
const snakeCase = [lodash / case-converter / homebrew]
const snakeCasedObject = Object.keys(obj).reduce((result, key) => ({
...result,
[snakeCase(key)]: obj[key],
}), {})
jsfiddle
//This function will rename one property to another in place
Object.prototype.renameProperty = function (oldName, newName) {
// Do nothing if the names are the same
if (oldName == newName) {
return this;
}
// Check for the old property name to avoid a ReferenceError in strict mode.
if (this.hasOwnProperty(oldName)) {
this[newName] = this[oldName];
delete this[oldName];
}
return this;
};
//rename this to something like camelCase to snakeCase
function doStuff(object) {
for (var property in object) {
if (object.hasOwnProperty(property)) {
var r = property.replace(/([A-Z])/, function(v) { return '_' + v.toLowerCase(); });
console.log(object);
object.renameProperty(property, r);
console.log(object);
}
}
}
//example object
var camelCased = {firstName: 'Jon', lastName: 'Smith'};
doStuff(camelCased);
Note: remember to remove any and all console.logs as they aren't needed for production code