Обновление вложенных объектов JS без перезаписи отсутствующих свойств

#javascript

#javascript

Вопрос:

РЕДАКТИРОВАТЬ: Спасибо 4 всем замечательным, разнообразным ответам — я выбрал решение, которое сработало для меня даже после того, как я понял, что мне нужно больше требований: мне также нужно было добавить новые свойства, а также для работы с массивами в объектах.

Вот что я хочу сделать: обновить один объект с помощью другого.
Вот некоторые ограничения:

  • Должны быть обновлены только свойства, которые есть у нового объекта (и которые также есть у исходного объекта), остальные свойства должны оставаться неизменными, А НЕ УДАЛЯТЬСЯ
  • Вложенные объекты также должны обновляться таким же образом

Моя проблема в том, что я не знаю, как легко сделать это для вложенных объектов, потому typeof x === "object" что также возвращает true, например, для объектов даты.

Вот что я получил до сих пор:

 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) amp;amp;
      oObj.hasOwnProperty(prop)) {
      oObj[prop] = uObj[prop];
    }
  }
};

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

В настоящее время мой обновленный объект выглядит следующим образом:

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

Моя цель:

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

Комментарии:

1. updateObj.hasOwnProperty(prop) <= следует использовать uObj . Вероятно, это не проблема, но она несовместима.

2. Ваша логика проверяет, есть ли у объекта обновления и исходного объекта ключ для обновления. Я понимаю, что вы не хотите удалять ключи, но если вам требуется, чтобы ключ уже существовал в обоих местах, вы никогда не добавите новые.

3. @Taplar спасибо, я исправил это

4. @Taplar Я не говорил, что хочу добавить новые, и я этого не делаю. Но также не сказал, что я этого не хочу, поэтому я понял вашу точку зрения, обновил свой вопрос ^^

Ответ №1:

Это то решение, которое вам нужно (из https://gist.github.com/ahtcx/0cd94e62691f539160b32ecda18af3d6 ), на самом деле вы можете выполнить поиск в Google «глубокое слияние» или «рекурсивное слияние двух объектов javascript».

Я добавил ES6 sintax { …obj} чтобы обязательно клонировать объекты перед их объединением

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

Ответ №2:

Вы можете протестировать конструктор значений следующим образом:

 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 amp;amp; value.constructor === Object amp;amp; patch[key]
      ? updateOriginal(original[key], patch[key])
      : (original[key] = patch[key]);
  });
}

updateOriginal(originalObj, updateObj);

console.log(originalObj); 

Ответ №3:

Вы можете использовать рекурсивный подход с reduce методом и for..in циклом, а также проверить, соответствует ли тип объекта Date . Это решение не будет работать с массивами, а также не будет изменять исходные данные.

 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) 

Ответ №4:

Это то, что работало для меня в прошлом. Вы можете немного очистить его и настроить для своих целей, но идея заключается в следующем:

 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) amp;amp; 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 amp;amp; typeof item === 'object' amp;amp; !Array.isArray(item));
}

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