Daskus
Daskus

Reputation: 969

Merge nested objects without mutating

I have the following 2 JavaScript Objects:

let o1 = {
  entities: {
    1: {
      text: "fooo",
      nested: {
        ids: [1, 2, 3],
        flag: true
      }
    }
  }
};

let o2 = {
  ids: [4, 5, 6]
}

I want to merge them without mutating them, to get an Object which looks like this:

let o3 = {
  entities: {
    1: {
      text: "fooo",
      nested: {
        ids: [1, 2, 3, 4, 5, 6],
        flag: true
      }
    }
  }
};

There could be n entities, but only the one defined by entityId should be affected. What I tried:

let entityId = 1;

let o3 = Object.assign({}, o1, {
         entities: Object.assign({}, o1.entities, {
           [entityId]: Object.assign({}, o1.entities[entityId].nested,
            { ids: [...o1.entities[entityId].nested.ids, ...o2.ids] }
          )
        })
      });

The problem is that text: "fooo",nested: completely disappear.

Is my aproach right? Could this code be optimized?

Upvotes: 2

Views: 3306

Answers (5)

Patrik
Patrik

Reputation: 1

I'd just do:

const merged = {
    entities: {
        [entityId]: {
            text: o1.entities[entityId].text,
            nested: {
                ids: [...o1.entities[entityId].nested.ids, ...o2.ids],
                flag: o1.entities[entityId].nested.flag
            }
        }
    }
}

Upvotes: 0

Hugo Silva
Hugo Silva

Reputation: 6938

Since you tagged Redux, I will assume you are using React and suggest you use the immutability helper - https://facebook.github.io/react/docs/update.html

Your code would look like this:

let o3 = update(o1, {
    entities: {
        [entityId]: {
            nested: {
                ids: {
                    $push: [4, 5, 6]
                }
            }
        }
    }
})

ES6

Your logic is correct, but you are missing the nested object in your code:

let o3 = Object.assign({}, o1, {
     entities: Object.assign({}, o1.entities, {
       [entityId]: Object.assign({}, o1.entities[entityId], { 
          nested : Object.assign({}, o1.entities[entityId].nested ,{
            ids: [...o1.entities[entityId].nested.ids, ...o2.ids] }) 
        }
      )
    })
  });

Upvotes: 3

Mike McG
Mike McG

Reputation: 386

Looks like you missed one layer of merging - your example code has entities.1.ids instead of entities.1.nested.ids. Try

let entityId = 1;

let o3 = Object.assign(
  {},
  o1,
  {
    entities: Object.assign(
      {},
      o1.entities,
      {
        [entityId]: Object.assign(
          {},
          o1.entities[entityId],
          Object.assign(
            {},
            o1.entities[entityId].nested,
            { ids: [...o1.entities[entityId].nested.ids, ...o2.ids] }
          )
        )
      }
    )
  }
);

At this point, you probably want to build up some intermediate variables from each of the Object.assign calls just so you can keep track of everything a little better.

Upvotes: 0

Jesse Amano
Jesse Amano

Reputation: 828

Well, your first problem is this bit:

{
       [entityId]: Object.assign({}, o1.entities[entityId].nested,

where you're copying the value of o1.entities[1].nested into the key o3.entities[1] and thus losing the other keys from the object held in o1.entities[1].

This is hard to see because, as the other comments have indicated, this isn't very pretty code.

You wanted to (and meant to) use o1.entities[entityId] instead, of course.

You have an additional problem, though, in that if you're only doing a deep copy on the part of o1 that you're currently interested in. Object.assign() will copy object references, instead of creating entirely fresh objects. So if you later modify the value of o3.entities[1].text, you will also be mutating o1.entities[1].

Upvotes: 0

som
som

Reputation: 2437

If you know your data isn't going to include any dates or methods you could use the JSON deep-clone trick:

let o3 = JSON.parse(JSON.stringify(o1));
Array.prototype.push.apply(o3.entities[1].nested.ids, o2.ids);

Upvotes: 1

Related Questions