Как обновить объект другим объектом в JavaScript - PullRequest
1 голос
/ 03 августа 2020

Я пытаюсь создать функцию, которая принимает целевой json объект исходный json объект (обновление). Правило состоит в том, что цель должна обновляться источником. Если ключи такие же, значение цели перезаписывается. Если ключ содержит точку ., мы рассматриваем его как вложенное обновление (пример 3). Если ключ обновления не существует в целевом объекте, мы создаем его.

Пример 1:

var target = {
    foo: 1,
}
var source = {
    foo: 22,
}
var output = {
    foo: 22
}

Пример 2:

var target = {
    foo: {
        bar: 1
    },
}
var source = {
    foo: {
        baz: 999
    },
}
var output = {
    foo: {
        baz: 999
    }
}

Пример 3:

var target = {
    foo: {
        bar: 1,
        baz: 99,
    },
}
var source = {
    'foo.bar': 22,
}
var output = {
    foo: {
        bar: 22,
        baz: 99
    }
}

Пример 4 (Массив)

Мы предполагаем, что числа являются недопустимым ключом для источника. Например, приведенный ниже источник никогда не появится

// this will never happen
let source = {
        1: 'something'
}

Однако обновление может обновить значение массива по указанному индексу, используя метод точки .

const source = {
    foo: [0, 1,2,3, {four: 4}]
}
const target = {
    'foo.0': 'zero',
    'foo.4.four': 'four',
    'foo.4.five': 5,
}
const output = {
    foo: ['zero', 1, 2, 3, {four: 'four', five: 5}]
}

Следовательно, массив не может изменить размер с помощью обновления точки, чтобы изменить размер, нам нужно перезаписать весь массив

Моя функция ниже отлично работает с примером 1, но я не могу заставить ее работать с примерами 2 и 3 .

Вместо этого я получаю:

var example2 = {
    bar: 1,
    baz: 999,
}


var example3 = {
    foo: {
        bar: 1,
        baz: 99,
    },
    'foo.bar': 22,
}

function deepMerge(target, source) {
    Object.entries(source).forEach(([key, value]) => {
        if (value && typeof value === 'object') {
            deepMerge(target[key] = target[key] || {}, value);
            return;
        }
        target[key] = value;
    });
    return target;
}

Ответы [ 3 ]

1 голос
/ 03 августа 2020

Я бы выполнил следующие действия

  1. Итерировать все source записи (пары ключ / значение)
  2. Разделить key на массив, отделенный . . Для ключей, отличных от , , это будет просто сам ключ
  3. Найти или создать внутренние объекты по пути, а затем присвоить новое значение

// utility function
const isObjectOrArray = obj => typeof obj === 'object' && obj !== null

const deepMerge = (target, source) => {
  // create a shallow copy because who wants to mutate source data
  const result = { ...target }
  
  // Iterate source entries
  Object.entries(source).forEach(([ path, val ]) => {
    const segments = path.split('.')
    // save the last segment to assign the value
    const last = segments.pop()
    
    // Find or create the deepest object by path
    const deepest = segments.reduce((obj, segment) => {
      // create objects if they aren't already
      if (!isObjectOrArray(obj[segment])) {
        // perhaps check if segment is a number and create an array instead
        // ¯\_(ツ)_/¯ I'm tired
        obj[segment] = {}
      }
      return obj[segment]
    }, result)
    
    // write the new value
    deepest[last] = val
  })
  return result
}

var target = {
  abc: 'abc',
  bar: {
    baz: 1
  },
  foo: {
    bar: 1,
    baz: 99,
  },
  nums: [0, 1,2,3, {four: 4}]
}
var source = {
  'foo.bar': 22,
  abc: 'def',
  'nums.0': 'zero',
  'nums.4.four': 'four',
  'nums.4.five': 5,
  bar: {
    bar: 'bar!'
  },
  'brand.new.object': 'value'
}

const output = deepMerge(target, source)
console.info(output)
.as-console-wrapper {max-height: none !important; top: 0;}
1 голос
/ 03 августа 2020

Это должно работать для каждого предоставленного вами примера:

function deepMerge(target: any, source: any) {
    Object.keys(source).forEach((key: string) => {
        if (key.includes('.')) {
            const nested: any = {}
            nested[key.split('.')[1]] = source[key]
            target[key.split('.')[0]] = deepMerge(target[key.split('.')[0]], nested)
            delete source[key]
        }
    });
    return { ...target, ...source }
}
0 голосов
/ 03 августа 2020

Здесь, если вы разбиваете {'foo.bar': 22} на { foo: {bar: 22}} перед тем, как передать его на вход в качестве источника, вы должны получить ожидаемый результат.

function breakItApart(obj) {
  Object.entries(obj).forEach(([key, value]) => {

        [firstKey, anotherKey] = key.split('.')
        if (anotherKey) {
            obj[firstKey] = obj[firstKey] || {};
            obj[firstKey][anotherKey] = value;
            delete obj[key];
        }
    });
    return obj; 
}

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

var target = {
    foo: {
        bar: 1,
        baz: 99,
    },
}
var source = {
    'foo.bar': 22,
}

const result = deepMerge(target, breakItApart(source));
console.log(result); 
>> { foo: { bar: 22, baz: 99 } }
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...