5

EDIT: Thanks 4 all the great, diverse answers - I chose the solution that worked for me even after I realized that I needed more requirements: I also needed new properties to be added and for it to work with arrays in objects as well.

Here's what I wanna do: Update one object through another one.
Here are some constraints:

  • Only properties that the new object has should be updated (and that the original object also has), the other properties should remain unchanged, NOT DELETED
  • Nested objects should also be updated in the same way

My Problem is that I don't know how to easily do this for nested objects because typeof x === "object" also returns true for Date objects for example.

Here's what I got so far:

let originalObj = {
  dateCreated: new Date(2021, 1, 10),
  value: "100",
  subObj: {
    count: 55,
    val: null
  }
};

let updateObj = {
  dateCreated: new Date(2021, 1, 11),
  subObj: {
    val: 90
  }
};

let updateOrignal = (oObj, uObj) => {
  for (let prop in uObj) {
    if (uObj.hasOwnProperty(prop) &&
      oObj.hasOwnProperty(prop)) {
      oObj[prop] = uObj[prop];
    }
  }
};

console.log(originalObj);
updateOrignal(originalObj, updateObj)
console.log(originalObj);

Currently my updated object looks like this:

{
  "dateCreated": "2021-02-10T23:00:00.000Z",
  "value": "100",
  "subObj": {
    "val": 90
  }
}

My goal:

{
  "dateCreated": "2021-02-10T23:00:00.000Z",
  "value": "100",
  "subObj": {
    "count": 55,
    "val": 90
  }
}
4
  • 1
    updateObj.hasOwnProperty(prop) <= should use uObj. Probably not the issue, but it is inconsistent. Commented Jan 20, 2021 at 21:08
  • Your logic is checking if the update object and the original object have the key to update. I understand you don't want to delete keys, but if you require the key to be pre existing in both places, you're never going to add new ones. Commented Jan 20, 2021 at 21:09
  • @Taplar thx, I corrected it Commented Jan 21, 2021 at 8:50
  • @Taplar I didn' say I want to add new ones, and I don't. But also didn't say that I don't want it, so I get your point, updated my question^^ Commented Jan 21, 2021 at 8:51

4 Answers 4

5

This is the solution you need (from https://gist.github.com/ahtcx/0cd94e62691f539160b32ecda18af3d6), actually you can search on google "deep merge" or "recursively merge two javascript objects"

I added ES6 sintax {...obj} to be sure to clone objects before merging them

let originalObj = {
  dateCreated: new Date(2021, 1, 10),
  value: "100",
  subObj: {
    count: 55,
    val: null
  }
};

let updateObj = {
  dateCreated: new Date(2021, 1, 11),
  subObj: {
    val: 90
  }
};

const merge = (target, source) => {
  // Iterate through `source` properties and if an `Object` set property to merge of `target` and `source` properties
  for (const 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
}

console.log(merge({ ...originalObj}, {...updateObj}));

Sign up to request clarification or add additional context in comments.

Comments

2

You could test the constructor of the values like this:

let originalObj = {
  dateCreated: new Date(2021, 1, 10),
  value: "100",
  subObj: {
    count: 55,
    val: null
  }
};

let updateObj = {
  dateCreated: new Date(2021, 1, 11),
  subObj: {
    val: 90
  }
};

let updateOriginal = (original, patch) => {
  Object.entries(patch).forEach(([key, value]) => {
    value && value.constructor === Object && patch[key]
      ? updateOriginal(original[key], patch[key])
      : (original[key] = patch[key]);
  });
}

updateOriginal(originalObj, updateObj);

console.log(originalObj);

Comments

2

You could use recursive approach with reduce method and for..in loop and also check if the type of object is Date. This solution will not work with arrays and also will not modify original data.

let originalObj = {
  dateCreated: new Date(2021, 1, 10),
  value: "100",
  subObj: {
    count: 55,
    val: null
  }
};

let updateObj = {
  dateCreated: new Date(2021, 1, 11),
  subObj: {
    val: 90
  }
};

function update(o1, o2) {
  return Object.entries(o1).reduce((r, e) => {
    for (let p in o2) {
      if ([o1[p], o2[p]].every(o => typeof o === 'object')) {
        r[p] = o2[p] instanceof Date ? o2[p] : update(r[p], o2[p])
      } else {
        r[p] = o2[p]
      }
    }

    return r
  }, { ...o1 })
}

const result = update(originalObj, updateObj)

console.log(result)

Comments

1

This is what worked for me in the past. You can clean it up a little and customize for your purposes but the idea is as follows:

let originalObj = {
  dateCreated: new Date(2021, 1, 10),
  value: "100",
  subObj: {
    count: 55,
    val: null
  }
};

let updateObj = {
  dateCreated: new Date(2021, 1, 11),
  subObj: {
    val: 90
  }
};

function mergeDeep(target, source) {
  if (isObject(target) && isObject(source)) {
    for (const key in source) {
      if (isObject(source[key])) {
        if (!target[key]) {
          Object.assign(target, { [key]: {} });
        }
        mergeDeep(target[key], source[key]);
      } else {
        Object.assign(target, { [key]: source[key] });
      }
    }
  }
}

function isObject(item) {
  return (item && typeof item === 'object' && !Array.isArray(item));
}

console.log(originalObj);
mergeDeep(originalObj, updateObj);
console.log(originalObj);

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.