Merge nested objects without mutating

2019-07-19 03:48发布

问题:

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?

回答1:

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] }) 
        }
      )
    })
  });


回答2:

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);


回答3:

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].



回答4:

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.



回答5:

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
            }
        }
    }
}