I'm trying to update an deeply nested object without overriding existing properties, but can't find an elegant way to do this, for example:
const data = {
items: {
one: {
active: false,
id: '123'
},
two: {
active: true
}
}
}
const updatedData = {
items: {
one: {
active: true
}
}
}
The end result should be:
{
items: {
one: {
active: true,
id: '123'
},
two: {
active: true
}
}
}
However, using Object.assign or spread operator, will replace items.one with only {active: true} and not retain the id. Is there a way to do this without recursively going through the object?
function merge(source, into) {
for(let key in into){
if(typeof into[key] === "object") {
merge(source[key] || (source[key] = {}), into[key]);
} else {
source[key] = into[key];
}
}
}
A recursive function makes it pretty simple. Iterate the properties of the new data. If both the target and source for a property reference an object, call the function recursively with the two objects. Otherwise just assign the source value to the target value.
const data = {
items: {
one: {
active: false,
id: '123'
},
two: {
active: true
}
}
}
const updatedData = {
items: {
one: {
active: true
}
}
}
updateWith(data, updatedData);
console.log(data);
function updateWith(target, newData) {
for (const [k, v] of Object.entries(newData)) {
if (typeof v === "object" && typeof target[k] === "object") {
updateWith(target[k], v);
} else {
target[k] = v;
}
}
}
Given that you reference an arbitrary data.items[key] object, you can do the following:
data.items[key] = Object.assign({}, data.items[key], updatedData.items[key]);
The above will replace the old object value with a new one, with properties copied from the original and ultimately replacing any properties provided by the updated data.
Related
I want to be able to compare two objects and remove the properties which values has not been changed.
prevObject = {
isActive: true,
userName: 'Patric',
settings: {
isUser: true
}
}
nextObject = {
isActive: true,
userName: 'Patric Surname',
settings: {
isUser: false
}
}
The result when comparing above should be:
{
userName: 'Patric Surname',
settings: {
isUser: false
}
}
I've tried some for in loops and such, but I haven't gotten it to work recursively. Sorry, I don't have any code to show that I've tried. I trashed it since I was a bit frustrated. I'm also stuck to pure ES5 on this one. So no lodash or underscore :)
Would be really glad for some help!
You'll need a recursive approach:
In ECMAScript-5 syntax:
function isObject(a) {
return typeof a === "object" && a;
}
function diff(a, b) {
var res = b instanceof Array ? [] : {};
for (var prop in b) {
if (!(prop in a) || (!isObject(a[prop]) || !isObject(b[prop])) && a[prop] !== b[prop]) {
res[prop] = b[prop];
} else {
var obj = diff(a[prop], b[prop]);
// If the recursive result is not an empty object, then use it
for (var _ in obj) {
res[prop] = obj;
break; // We only need 1 iteration
}
}
}
return res;
}
var prevObject = {isActive: true,userName: 'Patric',settings: {isUser: true }};
var nextObject = { isActive: true,userName: 'Patric Surname',settings: {isUser: false}};
console.log(diff(prevObject, nextObject));
This function has some limited support for arrays. Array elements are only considered the same if their content is the same and the index where they occur in the array. This means that a resulting array may have gaps ("sparse").
var prevObject = {
isActive: true,
userName: 'Patric',
settings: {
isUser: true
}
};
var nextObject = {
isActive: true,
userName: 'Patric Surname',
settings: {
isUser: false
}
};
var changes = Object.entries(nextObject).reduce((acc,cv)=>{
if(JSON.stringify(nextObject[cv[0]]) != JSON.stringify(prevObject[cv[0]]))
acc.push(cv);
return acc;
},[]).reduce((acc,cv)=>{
acc[cv[0]]=cv[1];
return acc;
},{});
console.log(changes);
Assuming both prevObject and nextObject have the same properties.
Object.entries of nextObject returns an Array of [key, value]s.
Initialize return value of reduce on that Array to an empty Array - [].
Fill it with properties if their JSON.stringify are different.
JSON.stringify comparison relieves us from checking type, etc. For example: [1,2,3] is NOT equal [1,2,3], but JSON.stringify([1,2,3])==JSON.stringify([1,2,3]) IS. Same for objects, etc.
We use reduce on the last created Array using {} as initial value, filling with properties (cv[0]) and their values (cv[1]) back into an Object.
In Firestore you can update fields in nested objects by a dot notation (https://firebase.google.com/docs/firestore/manage-data/add-data?authuser=0#update_fields_in_nested_objects). I wonder how to make that work in Typescript / Javascript.
For example the following object:
const user = {
id: 1
details: {
name: 'Max',
street: 'Examplestreet 38',
email: {
address: 'max#example.com',
verified: true
}
},
token: {
custom: 'safghhattgaggsa',
public: 'fsavvsadgga'
}
}
How can I update this object with the following changes:
details.email.verified = false;
token.custom = 'kka';
I already found that Lodash has a set function:
_.set(user, 'details.email.verified', false);
Disadvantage: I have to do this for every change. Is their already a method to update the object with an object (like firestore did)?
const newUser = ANYFUNCTION(user, {
'details.email.verified': false,
'token.custom' = 'kka'
});
// OUTPUT for newUser would be
{
id: 1
details: {
name: 'Max',
street: 'Examplestreet 38',
email: {
address: 'max#example.com',
verified: false
}
},
token: {
custom: 'kka',
public: 'fsavvsadgga'
}
}
Does anyone know an good solution for this? I already found more solutions if I only want to change one field (Dynamically set property of nested object), but no solution for more than one field with one method
I think you are stuck with using a function but you could write it yourself. No need for a lib:
function set(obj, path, value) {
let parts = path.split(".");
let last = parts.pop();
let lastObj = parts.reduce((acc, cur) => acc[cur], obj);
lastObj[last] = value;
}
set(user, 'details.email.verified', false);
if what you want to do is merge 2 objects then it is a bit trickier:
function forEach(target, fn) {
const keys = Object.keys(target);
let i = -1;
while (++i < keys.length) {
fn(target[keys[i]], keys[i]);
}
}
function setValues(obj, src) {
forEach(src, (value, key) => {
if (value !== null && typeof (value) === "object") {
setValues(obj[key], value);
} else {
obj[key] = value;
}
});
}
let obj1 = {foo: {bar: 1, boo: {zot: null}}};
let obj2 = {foo: {baz: 3, boo: {zot: 5}}};
setValues(obj1, obj2);
console.log(JSON.stringify(obj1));
One solution in combination with lodash _.set method could be:
function setObject(obj, paths) {
for (const p of Object.keys(paths)) {
obj = _.set(obj, p, paths[p]);
}
return obj;
}
Can you please help me how can I access "name" key from obj3. Please find below example.
I am looking for good approch, I dont want to do :
obj.obj1.obj2.obj3.name
var obj = {
obj1 : {
obj2: {
obj3: {
name: 'jhon'
}
}
}
}
Thanks!
You could theoretically destructure using es6 for example
const {obj1: {obj2: { obj3: {name: b}}}} = obj
console.log(b) //jhon
You can use a recursive function that returns the first non object element.
Obviously this functions works only for structures where the nested objects contains only one object or one value.
var obj = {
obj1 : {
obj2: {
obj3: {
name: 'jhon'
}
}
}
}
const getName = (obj) => {
if (typeof obj[Object.keys(obj)] === 'object') {
return getName(obj[Object.keys(obj)])
} else {
return obj[Object.keys(obj)]
}
}
getName(obj)
I have this json object returned from an API that has a few quirks, and I'd like to normalize it so I can process the input the same for every response. These means getting rid of superfluous keys:
Response:
{
_links: {...},
_embedded: {
foo: [
{
id: 2,
_embedded: {
bar: []
}
}
]
}
}
So I'd like to remove all the _embedded keys and flatten it, like so:
{
_links: {...},
foo: [
{
id: 2,
bar: []
}
]
}
This is what I have at the moment, but it only works for the top level and I don't think it'll play well with arrays.
_.reduce(temp1, function(accumulator, value, key) {
if (key === '_embedded') {
return _.merge(accumulator, value);
}
return accumulator[key] = value;
}, {})
Loop in recursion on all of your keys, once you see a key which start with _
simply remove it.
Code:
var
// The keys we want to remove from the Object
KEYS_TO_REMOVE = ['_embedded'],
// The data which we will use
data = {
_links: {'a': 1},
_embedded: {
foo: [
{
id: 2,
_embedded: {
bar: []
}
},
{
id: 3,
_embedded: {
bar: [
{
id: 4,
_embedded: {
bar: []
}
}
]
}
}
]
}
};
/**
* Flatten the given object and remove the desired keys if needed
* #param obj
*/
function flattenObject(obj, flattenObj) {
var key;
// Check to see if we have flatten obj or not
flattenObj = flattenObj || {};
// Loop over all the object keys and process them
for (key in obj) {
// Check that we are running on the object key
if (obj.hasOwnProperty(key)) {
// Check to see if the current key is in the "black" list or not
if (KEYS_TO_REMOVE.indexOf(key) === -1) {
// Process the inner object without this key
flattenObj[key] = flattenObject(obj[key], flattenObj[key]);
} else {
flattenObject(obj[key], flattenObj);
}
}
}
return flattenObj;
}
console.log(flattenObject(data));
So, basically you already have almost all of the code you need. All we have to do is wrap it in a function so we can use recursion. You'll see we only add a check to see if it is an object, if it is, we already have a function that knows how to flatten that object, so we'll just call it again with the key that we need to flatten.
function flatten(temp1) { // Wrap in a function so we can use recursion
return _.reduce(temp1, function(accumulator, value, key) {
if (key === '_embedded') {
return _.merge(accumulator, value);
} else if (value !== null && typeof value === 'object') // Check if it's another object
return _.merge(accumulator, flatten(value)) // Call our function again
return accumulator[key] = value;
}, {})
}
I'll be able to test it in a bit, but this should be what you need.
Got it!
function unEmbed(data) {
return _.reduce(data, function(accumulator, value, key) {
const returnableValue = _.isObject(value) ? unEmbed(value) : value;
if (key === 'embedded') {
return _.merge(accumulator, returnableValue);
}
accumulator[key] = returnableValue;
return accumulator;
}, {});
}
Problem before I was returning return accumulator[key] = returnableValue, which worked out to be return returnableValue.
I currently have this object:
var obj = {
1: {
title: 'test',
children: {
2: {
title: 'test2',
children: {}
},
3: {
title: 'test3',
children: {}
}
}
}
};
The whole idea is I make a function to add an item to this object. As parameter I send the parent.
Now, I was wondering how I would get the right item object. For example if I send parent '2', it would get 2: from the children of 1:. The only way I can think of is a for loop, but I don't know if there's a more efficient way. The children can be extended even more, so a parent has children, those children have children endlessly. That's the whole idea at least.
I think with a few items a for loop is okay, but I think if I have over 50 items it's already slow, and it'll even be slower with more.
This solution use Object.keys() for getting all keys of the given object and an array iteration with short ciruit Array.prototype.some() looks for the key. If found the reference is returned, otherwise the item is checked for an object. If so the object reference is taken for a new search with getReference().
var obj = { 1: { title: 'test', children: { 2: { title: 'test2', children: {} }, 3: { title: 'test3', children: {} } } } };
function getReference(o, p) {
var r;
Object.keys(o).some(function (k) {
if (k === p) {
r = o[k];
return true;
}
if (typeof o[k] === 'object') {
r = getReference(o[k], p);
return !!r;
}
});
return r;
}
var x = getReference(obj, '2');
document.write(x.title);
If you want adding to be fast, you can preserve indexes of your child nodes in object or map (ES6). It could look like this:
function Tree() {
this.data = {};
this.indexes = {0: this.data};
}
Tree.prototype = {
addNode: function(parentIndex, index, node) {
// handle cases when parentIndex does not exist
// handle cases when index already exists
this.indexes[index] = node;
var parent = this.indexes[parentIndex];
parent.children = parent.children || {};
parent.children[index] = node;
}
}
var tree = new Tree();
tree.addNode(0, 1, { title: 'test' });
tree.addNode(1, 2, { title: 'test2' });
tree.addNode(1, 3, { title: 'test3' });
console.log(tree.data);