I am using lodash to manipulate a JSON object. I am not against using Vanilla JS but since I am working on a PoC for now, I am just looking for the fastest solution to test.
So here is the problem I am facing: I want to be able to easily push an element to an array anywhere in an object, and it should create automatically all the missing nodes, including the last array.
For example, let's say I have an empty object, and I would like to create a function that can populate my object with the right values, for example:
let dl = {};
customPush(dl, 'a.b', { c: 3, d: 4 });
// or
customPush(dl, ['a', 'b'], { c: 3, d: 4 });
Should create:
dl = {
a: {
b: [{
c: 3,
d: 4
}]
}
}
This is everything I tried but none of them are working:
function customPush(obj, path, item) {
// This is just assigning the item to the path, not pushing to a new array
_.set(dl, path, item);
// This one is not doing anything visible
_.get(dl, path, []).push(item);
// Pushing in this one doesn't work with a path like 'a.b'
if (_.has(dl, path)) {
dl.path.push(item);
} else {
_.set(dl, path, [item]);
}
// Any idea?
...
}
Thank you very much for your help.
Your attempt here is very close:
// Pushing in this one doesn't work with a path like 'a.b'
if (_.has(dl, path)) {
dl.path.push(item);
} else {
_.set(dl, path, [item]);
}
You simply need to use _.get if the array is there and _.set if it isn't. You are already doing the latter part.
function customPush(obj, path, item) {
if (_.has(obj, path)) {
let arr = _.get(obj, path);
arr.push(item)
} else {
_.set(obj, path, [item]);
}
}
let objOne = { }
let objTwo = { a: [] }
let objThree = {
a: {
b: {
c: {
}
}
}
}
let objFour = {
a: {
b: {
c: {
d: []
}
}
}
}
customPush(objOne, "a", "item");
console.log("objOne", objOne);
customPush(objTwo, "a", "item");
console.log("objTwo", objTwo);
customPush(objThree, "a.b.c.d", "item");
console.log("objThree", objThree);
customPush(objFour, "a.b.c.d", "item");
console.log("objFour", objFour);
<script src="https://cdn.jsdelivr.net/npm/lodash#4.17.11/lodash.min.js"></script>
Worth noting that this only works if the key either doesn't exist or it it's value is an array. If you give the path to an existing key with a non-array value you'd get an error. You can check that using _.isArray but I am not sure what you want to do if a key exists and does not hold an array.
Related
Suppose I have some key-value object. I want to destructure dynamically from some key such that I can remove it and just get the remaining items in a new object.
const omit = (obj, key) => {
const { [key], ...rest } = obj // Syntax error
return rest
}
omit({ b: 1, c: 2, d: 3 }, 'd')
// desired output { b: 1, c: 2 }
Is there a way to do that?
Disclaimer: I know there are lots of workarounds but would like to do it with destructuring.
In order to destructure on a dynamic key you will need to provide an alias for JS to bind that value to.
Firefox even gives you a helpful error message here:
const omit = (obj, key) => {
const { [key]: _, ...rest } = obj
// CHANGE -----^
return rest
}
console.log(omit({ b: 1, c: 2, d: 3 }, 'd'))
You can rename the variables when destructuring, and the left side (preexisting name) can be in brackets like you want.
let {[key]: omitted, ...rest} = a;
I have a nested object. I need to filter them out by property of the child object but only get the keys.
I have tried so far to first, inject a property id into each child object and assign the object's key as its value. Then proceed to filter the object, compare property if it will match with the query, then return the injected property id.
let test_obj = {
A: {
a: 1,
b: 1,
},
B: {
a: 1,
b: 2,
},
C: {
a: 1,
b: 3,
}
}
let identify = (e) => {
for (e of Object.entries(e)){
key = e[0];
val = e[1];
val.id = key;
console.log(e);
}
}
identify(test_obj);
let query = (test_obj,prop,val) => (Object.values(test_obj).filter(o => o[prop] == val).map(o=>o.id));
let result = query(test_obj,"b",2);
console.log(result)
It currently return my desired results, yet I feel like I cheated. Is there a way to do this without having to inject another property to determine the key? I feel like I'm missing something, but I can't wrap my head around this.
Instead of adding an additional key, and then filtering values, you can filter the keys like this instead:
const test_obj = {
A: {
a: 1,
b: 1,
},
B: {
a: 1,
b: 2,
},
C: {
a: 1,
b: 3,
}
}
const query = (obj, prop, val) => Object.keys(obj).filter(k => obj[k][prop] === val);
console.log(query(test_obj, "b", 2))
A more elegant solution is to use the reduce functionality, which you can (and always should) use if you find yourself using filter and map:
function findKeysForValue(test_obj, value) {
return Object.entries(test_obj).reduce((myKeys, [objKey, outerValue]) => {
if (Object.values(outerValue).find(nestedValue => nestedValue === value)) {
return [...myKeys, objKey];
}
return myKeys;
}, []);
}
Consider a function returns an nested object and I want to modify the property inside the nested object.
In the below example, I'm calling the function many times or I need to store it in a temporary variable. Is there a way to invoke only once inside the braces and spread/modify inside the same object many times.
const getObject = () => {
return {
a: {
b: {
c: 1,
d: 2,
}
},
e: 3
}
}
var modifiedD = {
...getObject(),
a: {
b: {
...getObject().a.b,
d: 4
}
}
}
console.log(modifiedD);
when declaring a key after ...getObject() it replace the whole value. It does not merge the inner object behind a.
So you could do it as you have done and call getObject() multiple time.
An other solution could be to handle it using a function of your own merging the objects, like :
function mergeObjects(obj1, obj2) {
// We are going to copy the value of each obj2 key into obj1
Object.keys(obj2).forEach((x) => {
// If we have an object, we go deeper
if (typeof obj2[x] === 'object') {
if (obj1[x] === void 0) {
obj1[x] = {};
}
mergeObjects(obj1[x], obj2[x]);
} else {
obj1[x] = obj2[x];
}
});
return obj1;
}
const getObject = () => {
return {
a: {
b: {
c: 1,
d: 2,
}
},
e: 3
}
}
const modifiedD = mergeObjects(getObject(), {
a: {
b: {
d: 4,
},
},
});
console.log(modifiedD);
WARNING, the function I have made mutate the object which may not be the best answer
Or call it only once and then set the keys one by one like :
const getObject = () => {
return {
a: {
b: {
c: 1,
d: 2,
}
},
e: 3
}
}
const modifiedD = getObject();
modifiedD.a.b.d = 4;
console.log(modifiedD);
Further to my previous answer, as Grégory NEUT pointed out you could have a lot larger complexity.
If so, you could simply create two objects and then merge them. I found a function code snippet to be able to do that using Object.assign
Example:
const getObject = () => {
return {
a: {
b: {
c: 1,
d: 2,
}
},
e: 3
}
}
var modifiedD = getObject();
var newD = {
a: {
b: {
d: 4
},
y: 1
},
z: 20
}
/** TAKEN FROM https://gist.github.com/ahtcx/0cd94e62691f539160b32ecda18af3d6 **/
// Merge a `source` object to a `target` recursively
const merge = (target, source) => {
// Iterate through `source` properties and if an `Object` set property to merge of `target` and `source` properties
for (let key of Object.keys(source)) {
if (source[key] instanceof Object) Object.assign(source[key], merge(target[key], source[key]))
}
// Join `target` and modified `source`
Object.assign(target || {}, source)
return target
}
modifiedD = merge(modifiedD, newD);
console.log(modifiedD);
You can try the following:
getParentObj(path, obj) {
return path.split('.').reduce((o,i)=>o[i], obj);
}
const parent = getParentObj('a.b', getObject());
parent[d] = 24;
var data = {
'id': 'object1',
'sceneCapability': {
'updatedAt': '2017-06-19T20:52:45.688Z'
'currentScene': {
'value': {
'number': 1,
'name': '1'
}
},
'outOfTune': {
'value': false
}
},
'lightingCapability': {
'intensity': {
'value': 0
}
},
'tiltCapability': {
'command': {
'value': 'NO'
},
'position': {
'value': 0
}
}
// like this I have different types of more than 20 Capabilities
};
How can I write a generic method to parse this Object? I need to get currentScene value, outOfTune, intensity, command, position, etc...
Sometimes I get only one capability and sometime I get more than 20 capabilities.
I want to avoid doing something like this because in future there might be hundreds of different capabilities
if (obj.lightingCapability && obj.lightingCapability.intensity) {
console.log(obj.lightingCapability.intensity.value)
}
if (device.sceneCapability && device.sceneCapability.outOfTune) {
// do something
}
Output I want something like
currentScene:1,
outOfTune: false,
intensity: 0,
command: 'NO',
position: 0
Maybe something like this will work for you?
A helper function that finds the property you need and returns null if anything along the chain doesn't exist. I added two 'different' versions in case you don't like the array of property names.
var object = {
a: {
b: {
c: {
d: 10
}
}
}
};
function getValue(object, propertyPath) {
var o = object;
var pLen = propertyPath.length;
for (var i = 0; i < pLen; i++) {
var propertyName = propertyPath[i];
if (!o.hasOwnProperty(propertyName))
return undefined;
o = o[propertyName];
}
return o;
}
function getValueFromString(object, path) {
return this.getValue(object, path.split('.'));
}
console.log(getValue(object, ['a', 'b', 'c', 'd'])); //logs 10
console.log(getValueFromString(object, 'a.b.c.d')); //logs 10
console.log(getValue(object, ['a', 'b', 'c', 'e'])); //logs undefined
Based on the discussion we had in the comments of my first answer I realized you meant something different. This should do the trick:
var object = {
a: {
b: {
c: {
value: 10
},
d: {
e: {
value: 20
}
}
}
}
};
function logAllValues(object) {
for (var p in object) {
var o = object[p];
if (o.value)
console.log(p + ': ' + o.value);
else
logAllValues(o);
}
}
logAllValues(object); //logs c:10 and e:20
A slightly hacky way to do this would be to create a helper function that allows the key chain to be passed in as a string and loop over it. For example
function getValue(obj, keyChain){
var keys = keyChain.split('.');
do {
var key = keys.shift();
if (!obj.hasOwnProperty(key)){
return undefined;
}
obj = obj[key];
} while (keys.length > 0);
return obj;
}
getValue(data, "lightingCapability.intensity.value")
I think you just need to install lodash#get
npm i --save lodash.get
var get = require('lodash.get');
if(get('foo.baz.foobaz')) {
alert('yep');
}
but you always will need to know all the paths you need in advance.
Re-implementing this well community tested method will end up in re-inventing the wheel, so, just install and use it.
you can implement some thing like this using ES6 try and catch block
var object = {
a: {
b: {
c: {
value: 10
},
d: {
e: {
value: 20
}
}
}
}
};
function getValue(jsObject) {
try {
return jsObject();
} catch (e) {
return undefined;
}
}
// use it like this
getValue(() => object.a.b); // returns Object {c: Object, d: Object}
getValue(() => object.a.b.c); // returns Object {value: 10}
getValue(() => object.a.b.x); // returns undefined
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}}