programing

얕은 머지 대신 깊은 머지 방법을 알려주세요.

minecode 2022. 10. 11. 21:24
반응형

얕은 머지 대신 깊은 머지 방법을 알려주세요.

Object.assign과 Object spread는 모두 얕은 Marge만 수행합니다.

문제의 예:

// No object nesting
const x = { a: 1 }
const y = { b: 1 }
const z = { ...x, ...y } // { a: 1, b: 1 }

결과는 예상대로입니다.그러나 이것을 시도하면:

// Object nesting
얕은 머지 대신 깊은 머지 방법을 알려주세요.const x = { a: { a: 1 } }
const y = { a: { b: 1 } }
const z = { ...x, ...y } // { a: { b: 1 } }

대신

{ a: { a: 1, b: 1 } }

얻을 수 있다

{ a: { b: 1 } }

확산 구문은 한 단계 깊이에 불과하기 때문에 x는 완전히 덮어씁니다.은 것은와와같같 this this this this this 。Object.assign().

방법이 있을까요?

오래된 문제인 것은 알지만 ES2015/ES6에서 생각해 낼 수 있는 가장 쉬운 해결책은 Object.assign()을 사용하는 것이었습니다.

이것이 도움이 되기를 바랍니다.

/**
 * Simple object check.
 * @param item
 * @returns {boolean}
 */
export function isObject(item) {
  return (item && typeof item === 'object' && !Array.isArray(item));
}

/**
 * Deep merge two objects.
 * @param target
 * @param ...sources
 */
export function mergeDeep(target, ...sources) {
  if (!sources.length) return target;
  const source = sources.shift();

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

  return mergeDeep(target, ...sources);
}

사용 예:

mergeDeep(this, { a: { b: { c: 123 } } });
// or
const merged = mergeDeep({a: 1}, { b : { c: { d: { e: 12345}}}});  
console.dir(merged); // { a: 1, b: { c: { d: [Object] } } }

이에 대한 불변의 버전은 아래 답변에서 확인할 수 있습니다.

이로 인해 순환 기준에서 무한 재귀가 발생한다는 점에 유의하십시오.이 문제에 직면할 것 같다면 순환 참조를 탐지하는 방법에 대한 몇 가지 훌륭한 답이 있습니다.

Lodash Marge를 사용할 수 있습니다.

var object = {
  'a': [{ 'b': 2 }, { 'd': 4 }]
};

var other = {
  'a': [{ 'c': 3 }, { 'e': 5 }]
};

console.log(_.merge(object, other));
// => { 'a': [{ 'b': 2, 'c': 3 }, { 'd': 4, 'e': 5 }] }
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.21/lodash.min.js"></script>

호스트 객체 또는 값 가방보다 복잡한 모든 종류의 객체에 관한 문제는 간단하지 않습니다.

  • getter를 호출하여 값을 얻습니까, 아니면 속성 설명자에 복사합니까?
  • 병합 대상이 설정자(자체 특성 또는 원형 체인)를 가지고 있다면?값이 이미 존재한다고 생각하십니까, 아니면 설정자에게 연락하여 현재 값을 업데이트하시겠습니까?
  • 자체 복사 기능을 호출하거나 복사합니까?정의된 시점에 스코프 체인 내의 어떤 것에 따라 바인딩된 함수 또는 화살표 함수가 되는 경우에는 어떻게 해야 합니까?
  • DOM 노드 같은 거라면요?단순한 오브젝트로 취급하지 않고, 그 모든 속성을 심도 있게 통합하고 싶은 것은 확실합니다.
  • 어레이, 맵, 세트 등 '무제한' 구조에 어떻게 대처해야 합니까?이미 존재한다고 생각하십니까?아니면 통합도 가능합니까?
  • 어떻게 헤아릴 수 없는 자신의 재산에 대처할 것인가?
  • 새로운 서브트리는 어떨까요?단순히 레퍼런스 또는 딥 클론을 통해 할당하시겠습니까?
  • 냉동/불안함/불안함 물체를 처리하는 방법

또 하나 유의해야 할 것은 사이클을 포함하는 객체 그래프입니다.보통 . 그냥 해 두세요.단순히 보관해 주세요.Set하지만 종종 잊혀지곤 합니다.

기본 값과 단순한 개체(구조화 클론 알고리즘이 처리할 수 있는 최대 유형)만을 병합 소스로 예상하는 딥 병합 함수를 작성해야 할 수 있습니다.처리할 수 없는 것이 발견되었을 경우 또는 완전한 병합 대신 참조에 의해 할당되었을 경우 던집니다.

즉, 만능 알고리즘은 존재하지 않습니다.사용 사례를 커버하는 라이브러리 방법을 찾거나 직접 롤링을 해야 합니다.

다음은 @Salakar 응답의 불변 버전(입력 변경 없음)입니다.기능적인 프로그래밍 타입의 일을 할 때 편리합니다.

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

export default function mergeDeep(target, source) {
  let output = Object.assign({}, target);
  if (isObject(target) && isObject(source)) {
    Object.keys(source).forEach(key => {
      if (isObject(source[key])) {
        if (!(key in target))
          Object.assign(output, { [key]: source[key] });
        else
          output[key] = mergeDeep(target[key], source[key]);
      } else {
        Object.assign(output, { [key]: source[key] });
      }
    });
  }
  return output;
}

업데이트 2022:

아래 코멘트에서 설명한 다양한 머지/클론 요건에 대응하기 위해 머지사이언을 작성했습니다.이하의 컨셉에 근거해, 어레이의 처리나 Marge 전후의 속성 검사/변경을 유연하게 실시할 수 있습니다.또한 lodash.merge(5.1k min+gzip)등의 동종의 유틸리티에 비해, 큰폭으로 소형(1.5k min+gzip)입니다.


원답:

이 문제는 아직 진행 중이기 때문에 다음 접근방식을 제시하겠습니다.

  • ES6/2015
  • 불변(원래 개체는 수정하지 않음)
  • 어레이 처리(어레이 연결)

/**
* Performs a deep merge of objects and returns new object. Does not modify
* objects (immutable) and merges arrays via concatenation.
*
* @param {...object} objects - Objects to merge
* @returns {object} New object with merged key/values
*/
function mergeDeep(...objects) {
  const isObject = obj => obj && typeof obj === 'object';
  
  return objects.reduce((prev, obj) => {
    Object.keys(obj).forEach(key => {
      const pVal = prev[key];
      const oVal = obj[key];
      
      if (Array.isArray(pVal) && Array.isArray(oVal)) {
        prev[key] = pVal.concat(...oVal);
      }
      else if (isObject(pVal) && isObject(oVal)) {
        prev[key] = mergeDeep(pVal, oVal);
      }
      else {
        prev[key] = oVal;
      }
    });
    
    return prev;
  }, {});
}

// Test objects
const obj1 = {
  a: 1,
  b: 1, 
  c: { x: 1, y: 1 },
  d: [ 1, 1 ]
}
const obj2 = {
  b: 2, 
  c: { y: 2, z: 2 },
  d: [ 2, 2 ],
  e: 2
}
const obj3 = mergeDeep(obj1, obj2);

// Out
console.log(obj3);

이미 많은 답변이 있고 안 될 거라는 댓글도 많죠.유일한 합의는 그것이 너무 복잡해서 아무도 그것에 대한 기준만들지 않았다는 이다.그러나 SO에서 인정된 답변의 대부분은 널리 사용되는 "간단한 속임수"를 드러냅니다.그래서 나처럼 전문가는 아니지만 Javascript의 복잡성에 대해 조금 더 파악하여 안전한 코드를 작성하고자 하는 사람들을 위해, 저는 좀 더 조명해 보겠습니다.

손을 더럽히기 전에 두 가지 사항을 명확히 하겠습니다.

  • [DISCLAIMER] 복사용 Javascript 오브젝트에 대한 깊은 루프 방법을 설명하고 일반적으로 너무 짧은 코멘트를 하는 기능을 아래에 제안합니다.생산 준비가 되어 있지 않습니다.알기 쉽게 하기 위해 원형 객체(세트 또는 충돌하지 않는 기호 속성으로 추적), 참조값 또는 딥 클론 복사, 불변의 대상 객체(다시 딥 클론?), 각 객체 유형의 케이스 바이 케이스 스터디, 접근자를 통한 속성 가져오기/설정 등의 다른 고려사항은 의도적으로 생략했습니다.또, 퍼포먼스 테스트도 하지 않았습니다.중요하긴 하지만, 여기서도 중요한 것은 아니기 때문입니다.
  • 병합 대신 복사 또는 할당 용어를 사용합니다.왜냐하면 내 생각에 합병은 보수적이고 충돌에 실패해야 하기 때문이다.여기에서는, 경합하는 경우는, 송신원이 수신처를 덮어쓰도록 하고 있습니다.맘에 들다Object.assign

회:로:for..in ★★★★★★★★★★★★★★★★★」Object.keys의 소지가 있다

딥 카피를 작성하는 것은 매우 기본적이고 일반적인 관행으로 보이며, 우리는 단일 라인 또는 최소한 단순한 재귀로 빠른 성공을 거둘 수 있을 것으로 예상합니다.도서관이 필요하거나 100줄의 맞춤 함수를 쓸 필요는 없습니다.

내가 처음 Salakar의 대답을 읽었을 때, 나는 진정으로 내가 더 잘 그리고 더 단순할 수 있다고 생각했다.Object.assignx={a:1}, y={a:{b:1}}8472의 답을 읽고...그렇게 쉽게 도망칠 수는 없다. 이미 주어진 답을 개선한다고 해서 멀리 갈 수는 없다.

깊이 카피하고 재귀적인 것은 잠시 접어두자.매우 단순한 개체를 복사하기 위해 사람들이 속성을 어떻게(잘못) 해석하는지 생각해 보십시오.

const y = Object.create(
    { proto : 1 },
    { a: { enumerable: true, value: 1},
      [Symbol('b')] : { enumerable: true, value: 1} } )

Object.assign({},y)
> { 'a': 1, Symbol(b): 1 } // All (enumerable) properties are copied

((x,y) => Object.keys(y).reduce((acc,k) => Object.assign(acc, { [k]: y[k] }), x))({},y)
> { 'a': 1 } // Missing a property!

((x,y) => {for (let k in y) x[k]=y[k];return x})({},y)
> { 'a': 1, 'proto': 1 } // Missing a property! Prototype's property is copied too!

Object.keys열거할 수 없는 자체 속성, 기호 키 속성 및 모든 프로토타입의 속성을 생략합니다.만약 당신의 물건에 그것들이 없다면 괜찮을지도 모릅니다., 지지 but but지 but but but but but but but 。Object.assign는 자체 기호 키 열거형 속성을 처리합니다.커스텀 카피도 꽃망울을 잃었군요.

for..in에서는 소스, 프로토타입 및 완전한 프로토타입 체인의 속성을 사용자가 원하지 않아도(또는 모르는 사이에) 제공합니다.대상이 너무 많은 속성을 갖게 되어 프로토타입 속성과 자체 속성이 혼재될 수 있습니다.

있는데 Object.getOwnPropertyDescriptors,Object.getOwnPropertyNames,Object.getOwnPropertySymbols ★★★★★★★★★★★★★★★★★」Object.getPrototypeOf아마 잘못하고 있을 거야

기능을 작성하기 전에 고려해야 할 사항

먼저 Javascript 객체가 무엇인지 이해하십시오.Javascript에서 객체는 자체 속성과 (부모) 프로토타입 객체로 만들어집니다.프로토타입 객체는 자체 특성과 프로토타입 객체로 만들어집니다.시제품 체인을 정의하는 것 등이 있습니다.

은 한 의 키)string ★★★★★★★★★★★★★★★★★」symbol 디스크립터Descriptor')value ★★★★★★★★★★★★★★★★★」get/set및 "" 등의 enumerable를 참조해 주세요.

마지막으로, 많은 종류의 물건들이 있다.개체 날짜 또는 개체 함수와는 다르게 개체를 처리할 수 있습니다.

따라서, 상세한 카피를 작성할 때는, 적어도 다음의 질문에 대답할 필요가 있습니다.

  1. 깊이(재귀적 룩업에 적합) 또는 플랫(flat) 중 무엇을 고려해야 합니까?
  2. 복사할 속성을 선택하십시오.(수치 가능/수치 불가능, 문자열 키/수치/수치 키, 자체 속성/수치/수치...)

를 들어,는 "" " " " " " " " " " 이 " " 이 " " 입니다.object Object다른 생성자에 의해 작성된 다른 오브젝트는 상세하게 보기 위해 적절하지 않을 수 있기 때문에는 깊이가 있습니다.이 SO부터 커스터마이즈.

function toType(a) {
    // Get fine type (object, array, function, null, error, date ...)
    return ({}).toString.call(a).match(/([a-z]+)(:?\])/i)[1];
}

function isDeepObject(obj) {
    return "Object" === toType(obj);
}

나는 ★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★.options오브젝트: 복사 대상을 선택합니다(데모를 위해).

const options = {nonEnum:true, symbols:true, descriptors: true, proto:true};

제안된 기능

플런커에서 테스트할 수 있습니다.

function deepAssign(options) {
    return function deepAssignWithOptions (target, ...sources) {
        sources.forEach( (source) => {

            if (!isDeepObject(source) || !isDeepObject(target))
                return;

            // Copy source's own properties into target's own properties
            function copyProperty(property) {
                const descriptor = Object.getOwnPropertyDescriptor(source, property);
                //default: omit non-enumerable properties
                if (descriptor.enumerable || options.nonEnum) {
                    // Copy in-depth first
                    if (isDeepObject(source[property]) && isDeepObject(target[property]))
                        descriptor.value = deepAssign(options)(target[property], source[property]);
                    //default: omit descriptors
                    if (options.descriptors)
                        Object.defineProperty(target, property, descriptor); // shallow copy descriptor
                    else
                        target[property] = descriptor.value; // shallow copy value only
                }
            }

            // Copy string-keyed properties
            Object.getOwnPropertyNames(source).forEach(copyProperty);

            //default: omit symbol-keyed properties
            if (options.symbols)
                Object.getOwnPropertySymbols(source).forEach(copyProperty);

            //default: omit prototype's own properties
            if (options.proto)
                // Copy souce prototype's own properties into target prototype's own properties
                deepAssign(Object.assign({},options,{proto:false})) (// Prevent deeper copy of the prototype chain
                    Object.getPrototypeOf(target),
                    Object.getPrototypeOf(source)
                );

        });
        return target;
    }
}

다음과 같이 사용할 수 있습니다.

const x = { a: { a: 1 } },
      y = { a: { b: 1 } };
deepAssign(options)(x,y); // { a: { a: 1, b: 1 } }

lodash와 같은 도서관을 필요로 하지 않고 라이너 하나를 가지고 싶다면 deepmerge를 사용하는 것을 추천합니다.npm install deepmerge또는 deepmerge-ts(npm install deepmerge-ts를 참조해 주세요.

deepmerge (이므로) 높지만, TypeScript는 TypeScript를 사용하는 것이 .deepmerge-tsDeno에서도 사용할 수 있으며 이름에서 알 수 있듯이 TypeScript로 작성되어 있지만 설계상빠릅니다.

Import가 완료되면 다음 작업을 수행할 수 있습니다.

deepmerge({ a: 1, b: 2, c: 3 }, { a: 2, d: 3 });

갖기 위해

{ a: 2, b: 2, c: 3, d: 3 }

이것은 복잡한 오브젝트나 어레이에 적합합니다.이것은 진정한 만능 솔루션입니다.

lodash를 사용합니다.

import _ = require('lodash');
value = _.merge(value1, value2);

많은 답변이 수십 줄의 코드를 사용하거나 프로젝트에 새 라이브러리를 추가해야 하지만 재귀 기능을 사용할 경우 코드 4줄에 불과합니다.

function merge(current, updates) {
  for (key of Object.keys(updates)) {
    if (!current.hasOwnProperty(key) || typeof updates[key] !== 'object') current[key] = updates[key];
    else merge(current[key], updates[key]);
  }
  return current;
}
console.log(merge({ a: { a: 1 } }, { a: { b: 1 } }));

새 어레이 .위 버전은 이전 어레이 값을 새 어레이 값으로 덮어씁니다.하고 새 값을 "를 합니다.else if (current[key] instanceof Array && updates[key] instanceof Array) current[key] = current[key].concat(updates[key])else이제 준비가 다 됐어요

다음은 TypeScript 구현입니다.

export const mergeObjects = <T extends object = object>(target: T, ...sources: T[]): T  => {
  if (!sources.length) {
    return target;
  }
  const source = sources.shift();
  if (source === undefined) {
    return target;
  }

  if (isMergebleObject(target) && isMergebleObject(source)) {
    Object.keys(source).forEach(function(key: string) {
      if (isMergebleObject(source[key])) {
        if (!target[key]) {
          target[key] = {};
        }
        mergeObjects(target[key], source[key]);
      } else {
        target[key] = source[key];
      }
    });
  }

  return mergeObjects(target, ...sources);
};

const isObject = (item: any): boolean => {
  return item !== null && typeof item === 'object';
};

const isMergebleObject = (item): boolean => {
  return isObject(item) && !Array.isArray(item);
};

유닛 테스트:

describe('merge', () => {
  it('should merge Objects and all nested Ones', () => {
    const obj1 = { a: { a1: 'A1'}, c: 'C', d: {} };
    const obj2 = { a: { a2: 'A2'}, b: { b1: 'B1'}, d: null };
    const obj3 = { a: { a1: 'A1', a2: 'A2'}, b: { b1: 'B1'}, c: 'C', d: null};
    expect(mergeObjects({}, obj1, obj2)).toEqual(obj3);
  });
  it('should behave like Object.assign on the top level', () => {
    const obj1 = { a: { a1: 'A1'}, c: 'C'};
    const obj2 = { a: undefined, b: { b1: 'B1'}};
    expect(mergeObjects({}, obj1, obj2)).toEqual(Object.assign({}, obj1, obj2));
  });
  it('should not merge array values, just override', () => {
    const obj1 = {a: ['A', 'B']};
    const obj2 = {a: ['C'], b: ['D']};
    expect(mergeObjects({}, obj1, obj2)).toEqual({a: ['C'], b: ['D']});
  });
  it('typed merge', () => {
    expect(mergeObjects<TestPosition>(new TestPosition(0, 0), new TestPosition(1, 1)))
      .toEqual(new TestPosition(1, 1));
  });
});

class TestPosition {
  constructor(public x: number = 0, public y: number = 0) {/*empty*/}
}

deepmerge npm 패키지는 이 문제를 해결하기 위해 가장 널리 사용되는 라이브러리입니다.https://www.npmjs.com/package/deepmerge

여기, 직진;

Object.assign깊이가 깊고, 어레이에서도 기능하며, 수정이 가능합니다.

function deepAssign(target, ...sources) {
  for (source of sources) {
    for (let k in source) {
      let vs = source[k], vt = target[k]
      if (Object(vs) == vs && Object(vt) === vt) {
        target[k] = deepAssign(vt, vs)
        continue
      }
      target[k] = source[k]
    }
  }
  return target
}

x = { a: { a: 1 }, b: [1,2] }
y = { a: { b: 1 }, b: [3] }
z = { c: 3, b: [,,,4] }
x = deepAssign(x, y, z)

console.log(JSON.stringify(x) === JSON.stringify({
  "a": {
    "a": 1,
    "b": 1
  },
  "b": [ 1, 2, null, 4 ],
  "c": 3
}))

편집: 두 개체를 자세히 비교하는 새로운 방법에 대해 다른 곳에서 답변합니다.이 방법은 딥 머지에도 사용할 수 있습니다.임플란트를 원하시면 코멘트 https://stackoverflow.com/a/71177790/1919821를 달아주세요.

ES5를 이용하다는 두 가지 변수, 즉 기껏해야 하다를 .target ★★★★★★★★★★★★★★★★★」source" "" 유형이어야 . Target결과 객체가 됩니다. Target는 원래 속성을 모두 유지하지만 값은 변경될 수 있습니다.

function deepMerge(target, source) {
if(typeof target !== 'object' || typeof source !== 'object') return false; // target or source or both ain't objects, merging doesn't make sense
for(var prop in source) {
  if(!source.hasOwnProperty(prop)) continue; // take into consideration only object's own properties.
  if(prop in target) { // handling merging of two properties with equal names
    if(typeof target[prop] !== 'object') {
      target[prop] = source[prop];
    } else {
      if(typeof source[prop] !== 'object') {
        target[prop] = source[prop];
      } else {
        if(target[prop].concat && source[prop].concat) { // two arrays get concatenated
          target[prop] = target[prop].concat(source[prop]);
        } else { // two objects get merged recursively
          target[prop] = deepMerge(target[prop], source[prop]); 
        } 
      }  
    }
  } else { // new properties get added to target
    target[prop] = source[prop]; 
  }
}
return target;
}

케이스:

  • target이 없다source 「」,target
  • target 있다source 및 프로 property propertytarget&source 오브젝트), '' (4건 중 3건)이 아닙니다.target탕진하다
  • target 있다source속성 및 둘 다 오브젝트/오브젝트(나머지 케이스 1개)이며, 재귀는 2개의 오브젝트를 병합(또는 2개의 어레이를 연결)합니다.

다음 사항도 고려해야 합니다.

  1. array + obj = 어레이
  2. obj + 어레이 = obj
  3. obj + obj = obj (순차적으로 병합)
  4. 어레이 + 어레이 = 어레이 (콘센트)

예측 가능하고 기본 유형뿐만 아니라 배열 및 개체도 지원합니다.또, 2개의 오브젝트를 Marge 할 수 있기 때문에, reduced 기능을 통해서 2개 이상의 오브젝트를 Marge 할 수 있다고 생각합니다.

예를 들어 보겠습니다(필요한 경우 가지고수도 있습니다).

var a = {
   "a_prop": 1,
   "arr_prop": [4, 5, 6],
   "obj": {
     "a_prop": {
       "t_prop": 'test'
     },
     "b_prop": 2
   }
};

var b = {
   "a_prop": 5,
   "arr_prop": [7, 8, 9],
   "b_prop": 15,
   "obj": {
     "a_prop": {
       "u_prop": false
     },
     "b_prop": {
        "s_prop": null
     }
   }
};

function deepMerge(target, source) {
    if(typeof target !== 'object' || typeof source !== 'object') return false;
    for(var prop in source) {
    if(!source.hasOwnProperty(prop)) continue;
      if(prop in target) {
        if(typeof target[prop] !== 'object') {
          target[prop] = source[prop];
        } else {
          if(typeof source[prop] !== 'object') {
            target[prop] = source[prop];
          } else {
            if(target[prop].concat && source[prop].concat) {
              target[prop] = target[prop].concat(source[prop]);
            } else {
              target[prop] = deepMerge(target[prop], source[prop]); 
            } 
          }  
        }
      } else {
        target[prop] = source[prop]; 
      }
    }
  return target;
}

console.log(deepMerge(a, b));

브라우저의 콜스택 길이에는 제한이 있습니다.최신 브라우저는 매우 깊은 재귀 수준에서 오류를 발생시킵니다(수천 개의 중첩된 콜을 생각할 수 있습니다).또한 새로운 조건과 타입 체크를 추가하여 어레이 + 오브젝트 등의 상황을 자유롭게 처리할 수 있습니다.

ES5를 사용한 심플한 솔루션(기존 가치 덮어쓰기):

function merge(current, update) {
  Object.keys(update).forEach(function(key) {
    // if update[key] exist, and it's not a string or array,
    // we go in one level deeper
    if (current.hasOwnProperty(key) 
        && typeof current[key] === 'object'
        && !(current[key] instanceof Array)) {
      merge(current[key], update[key]);

    // if update[key] doesn't exist in current, or it's a string
    // or array, then assign/overwrite current[key] to update[key]
    } else {
      current[key] = update[key];
    }
  });
  return current;
}

var x = { a: { a: 1 } }
var y = { a: { b: 1 } }

console.log(merge(x, y));

방법이 있을까요?

npm 라이브러리를 솔루션으로 사용할 수 있는 경우 object-merge-advanced from your object-merge-advanced를 사용하면 익숙한 콜백 함수를 사용하여 객체를 완전히 Marge하고 모든 Marge 액션을 커스터마이즈/오버라이드할 수 있습니다.이 방법의 주요 개념은 단순한 통합이 아닙니다. 두 키가 같은 경우 이 값은 어떻게 됩니까?이 라이브러리는 두 키가 충돌할 때 이를 처리합니다.object-merge-advanced는 결합후 한 한 데이터를 로 하여 유형을 합니다.

가능한 한 많은 데이터를 보존하기 위해 객체 키 병합 가중 키 값 유형

첫 번째 입력 인수의 키는 #1, 두 번째 인수의 키는 #2로 표시됩니다.각 유형에 따라 결과 키 값으로 선택됩니다.그림에서 "개체"는 (배열 등이 아닌) 평범한 개체를 의미한다.

키가 충돌하지 않으면 모두 결과를 입력합니다.

스니펫에서 " " " 를 ,object-merge-advanced「 」 、 「 」 、 「 」:

const mergeObj = require("object-merge-advanced");
const x = { a: { a: 1 } };
const y = { a: { b: 1 } };
const res = console.log(mergeObj(x, y));
// => res = {
//      a: {
//        a: 1,
//        b: 1
//      }
//    }

이 알고리즘은 모든 입력 객체 키를 재귀적으로 통과하여 비교 및 빌드하고 새로운 병합 결과를 반환합니다.

다음 함수는 오브젝트의 상세 복사를 만듭니다.원시적인 복사, 어레이 및 오브젝트 복사를 망라합니다.

 function mergeDeep (target, source)  {
    if (typeof target == "object" && typeof source == "object") {
        for (const key in source) {
            if (source[key] === null && (target[key] === undefined || target[key] === null)) {
                target[key] = null;
            } else if (source[key] instanceof Array) {
                if (!target[key]) target[key] = [];
                //concatenate arrays
                target[key] = target[key].concat(source[key]);
            } else if (typeof source[key] == "object") {
                if (!target[key]) target[key] = {};
                this.mergeDeep(target[key], source[key]);
            } else {
                target[key] = source[key];
            }
        }
    }
    return target;
}

이 예제의 대부분은 너무 복잡한 것 같습니다.제가 작성한 TypeScript에서 사용하고 있습니다.대부분의 케이스에 대응하고 있다고 생각합니다(어레이를 통상의 데이터로 취급하고 있기 때문에, 어레이를 교환하기만 하면 됩니다.

const isObject = (item: any) => typeof item === 'object' && !Array.isArray(item);

export const merge = <A = Object, B = Object>(target: A, source: B): A & B => {
  const isDeep = (prop: string) =>
    isObject(source[prop]) && target.hasOwnProperty(prop) && isObject(target[prop]);
  const replaced = Object.getOwnPropertyNames(source)
    .map(prop => ({ [prop]: isDeep(prop) ? merge(target[prop], source[prop]) : source[prop] }))
    .reduce((a, b) => ({ ...a, ...b }), {});

  return {
    ...(target as Object),
    ...(replaced as Object)
  } as A & B;
};

만약을 위해 플레인 JS에서도 마찬가지입니다.

const isObject = item => typeof item === 'object' && !Array.isArray(item);

const merge = (target, source) => {
  const isDeep = prop => 
    isObject(source[prop]) && target.hasOwnProperty(prop) && isObject(target[prop]);
  const replaced = Object.getOwnPropertyNames(source)
    .map(prop => ({ [prop]: isDeep(prop) ? merge(target[prop], source[prop]) : source[prop] }))
    .reduce((a, b) => ({ ...a, ...b }), {});

  return {
    ...target,
    ...replaced
  };
};

여기 사용법을 보여주는 테스트 케이스가 있습니다.

describe('merge', () => {
  context('shallow merges', () => {
    it('merges objects', () => {
      const a = { a: 'discard' };
      const b = { a: 'test' };
      expect(merge(a, b)).to.deep.equal({ a: 'test' });
    });
    it('extends objects', () => {
      const a = { a: 'test' };
      const b = { b: 'test' };
      expect(merge(a, b)).to.deep.equal({ a: 'test', b: 'test' });
    });
    it('extends a property with an object', () => {
      const a = { a: 'test' };
      const b = { b: { c: 'test' } };
      expect(merge(a, b)).to.deep.equal({ a: 'test', b: { c: 'test' } });
    });
    it('replaces a property with an object', () => {
      const a = { b: 'whatever', a: 'test' };
      const b = { b: { c: 'test' } };
      expect(merge(a, b)).to.deep.equal({ a: 'test', b: { c: 'test' } });
    });
  });

  context('deep merges', () => {
    it('merges objects', () => {
      const a = { test: { a: 'discard', b: 'test' }  };
      const b = { test: { a: 'test' } } ;
      expect(merge(a, b)).to.deep.equal({ test: { a: 'test', b: 'test' } });
    });
    it('extends objects', () => {
      const a = { test: { a: 'test' } };
      const b = { test: { b: 'test' } };
      expect(merge(a, b)).to.deep.equal({ test: { a: 'test', b: 'test' } });
    });
    it('extends a property with an object', () => {
      const a = { test: { a: 'test' } };
      const b = { test: { b: { c: 'test' } } };
      expect(merge(a, b)).to.deep.equal({ test: { a: 'test', b: { c: 'test' } } });
    });
    it('replaces a property with an object', () => {
      const a = { test: { b: 'whatever', a: 'test' } };
      const b = { test: { b: { c: 'test' } } };
      expect(merge(a, b)).to.deep.equal({ test: { a: 'test', b: { c: 'test' } } });
    });
  });
});

기능이 부족하다고 생각되면 알려주세요.

딥 머지에는 $.extend(true, object1, object2)를 사용할 수 있습니다.true 은 두 개체를 재귀적으로 병합하고 첫 번째 개체를 수정함을 나타냅니다.

$syslog(true, target, object)

에서 불변성을 사용하고 있는 경우)을 사용하고 있는 경우사용할 수 있는 JSmergeDeep:

fromJS(options).mergeDeep(options2).toJS();

Javascript 함수의 훌륭한 라이브러리인 Ramda는 mergeDeepLeft와 mergeDeepRight를 가지고 있습니다.이것들 중 어느 것이든 이 문제에 꽤 잘 맞습니다.다음 문서를 참조하십시오.https://ramdajs.com/docs/ #merge Deep Left

문제의 구체적인 예에서는 다음을 사용할 수 있습니다.

import { mergeDeepLeft } from 'ramda'
const x = { a: { a: 1 } }
const y = { a: { b: 1 } }
const z = mergeDeepLeft(x, y)) // {"a":{"a":1,"b":1}}

줄여서

export const merge = (objFrom, objTo) => Object.keys(objFrom)
    .reduce(
        (merged, key) => {
            merged[key] = objFrom[key] instanceof Object && !Array.isArray(objFrom[key])
                ? merge(objFrom[key], merged[key] ?? {})
                : objFrom[key]
            return merged
        }, { ...objTo }
    )
test('merge', async () => {
    const obj1 = { par1: -1, par2: { par2_1: -21, par2_5: -25 }, arr: [0,1,2] }
    const obj2 = { par1: 1, par2: { par2_1: 21 }, par3: 3, arr: [3,4,5] }
    const obj3 = merge3(obj1, obj2)
    expect(obj3).toEqual(
        { par1: -1, par2: { par2_1: -21, par2_5: -25 }, par3: 3, arr: [0,1,2] }
    )
})

캐시된 redux 상태를 로드할 때 이 문제가 발생했습니다.캐시된 상태만 로드하면 상태 구조가 업데이트된 새 앱 버전에 오류가 발생합니다.

가 Lodash를 .merge이 기능을 사용합니다.

const currentInitialState = configureState().getState();
const mergedState = _.merge({}, currentInitialState, cachedState);
const store = configureState(mergedState);

여기 어레이를 지원하는 또 다른 기능이 있습니다.그것은 그들을 진정시킨다.

function isObject(obj) {
    return obj !== null && typeof obj === 'object';
}


function isPlainObject(obj) {
    return isObject(obj) && (
        obj.constructor === Object  // obj = {}
        || obj.constructor === undefined // obj = Object.create(null)
    );
}

function mergeDeep(target, ...sources) {
    if (!sources.length) return target;
    const source = sources.shift();

    if(Array.isArray(target)) {
        if(Array.isArray(source)) {
            target.push(...source);
        } else {
            target.push(source);
        }
    } else if(isPlainObject(target)) {
        if(isPlainObject(source)) {
            for(let key of Object.keys(source)) {
                if(!target[key]) {
                    target[key] = source[key];
                } else {
                    mergeDeep(target[key], source[key]);
                }
            }
        } else {
            throw new Error(`Cannot merge object with non-object`);
        }
    } else {
        target = source;
    }

    return mergeDeep(target, ...sources);
};

다음 기능을 사용합니다.

merge(target, source, mutable = false) {
        const newObj = typeof target == 'object' ? (mutable ? target : Object.assign({}, target)) : {};
        for (const prop in source) {
            if (target[prop] == null || typeof target[prop] === 'undefined') {
                newObj[prop] = source[prop];
            } else if (Array.isArray(target[prop])) {
                newObj[prop] = source[prop] || target[prop];
            } else if (target[prop] instanceof RegExp) {
                newObj[prop] = source[prop] || target[prop];
            } else {
                newObj[prop] = typeof source[prop] === 'object' ? this.merge(target[prop], source[prop]) : source[prop];
            }
        }
        return newObj;
    }

이것은 내가 생각할 수 있는 한 적은 코드를 사용하는 값싼 딥 머지입니다.각 소스는 이전 속성이 존재하는 경우 덮어씁니다.

const { keys } = Object;

const isObject = a => typeof a === "object" && !Array.isArray(a);
const merge = (a, b) =>
  isObject(a) && isObject(b)
    ? deepMerge(a, b)
    : isObject(a) && !isObject(b)
    ? a
    : b;

const coalesceByKey = source => (acc, key) =>
  (acc[key] && source[key]
    ? (acc[key] = merge(acc[key], source[key]))
    : (acc[key] = source[key])) && acc;

/**
 * Merge all sources into the target
 * overwriting primitive values in the the accumulated target as we go (if they already exist)
 * @param {*} target
 * @param  {...any} sources
 */
const deepMerge = (target, ...sources) =>
  sources.reduce(
    (acc, source) => keys(source).reduce(coalesceByKey(source), acc),
    target
  );

console.log(deepMerge({ a: 1 }, { a: 2 }));
console.log(deepMerge({ a: 1 }, { a: { b: 2 } }));
console.log(deepMerge({ a: { b: 2 } }, { a: 1 }));
// copies all properties from source object to dest object recursively
export function recursivelyMoveProperties(source, dest) {
  for (const prop in source) {
    if (!source.hasOwnProperty(prop)) {
      continue;
    }

    if (source[prop] === null) {
      // property is null
      dest[prop] = source[prop];
      continue;
    }

    if (typeof source[prop] === 'object') {
      // if property is object let's dive into in
      if (Array.isArray(source[prop])) {
        dest[prop] = [];
      } else {
        if (!dest.hasOwnProperty(prop)
        || typeof dest[prop] !== 'object'
        || dest[prop] === null || Array.isArray(dest[prop])
        || !Object.keys(dest[prop]).length) {
          dest[prop] = {};
        }
      }
      recursivelyMoveProperties(source[prop], dest[prop]);
      continue;
    }

    // property is simple type: string, number, e.t.c
    dest[prop] = source[prop];
  }
  return dest;
}

유닛 테스트:

describe('recursivelyMoveProperties', () => {
    it('should copy properties correctly', () => {
      const source: any = {
        propS1: 'str1',
        propS2: 'str2',
        propN1: 1,
        propN2: 2,
        propA1: [1, 2, 3],
        propA2: [],
        propB1: true,
        propB2: false,
        propU1: null,
        propU2: null,
        propD1: undefined,
        propD2: undefined,
        propO1: {
          subS1: 'sub11',
          subS2: 'sub12',
          subN1: 11,
          subN2: 12,
          subA1: [11, 12, 13],
          subA2: [],
          subB1: false,
          subB2: true,
          subU1: null,
          subU2: null,
          subD1: undefined,
          subD2: undefined,
        },
        propO2: {
          subS1: 'sub21',
          subS2: 'sub22',
          subN1: 21,
          subN2: 22,
          subA1: [21, 22, 23],
          subA2: [],
          subB1: false,
          subB2: true,
          subU1: null,
          subU2: null,
          subD1: undefined,
          subD2: undefined,
        },
      };
      let dest: any = {
        propS2: 'str2',
        propS3: 'str3',
        propN2: -2,
        propN3: 3,
        propA2: [2, 2],
        propA3: [3, 2, 1],
        propB2: true,
        propB3: false,
        propU2: 'not null',
        propU3: null,
        propD2: 'defined',
        propD3: undefined,
        propO2: {
          subS2: 'inv22',
          subS3: 'sub23',
          subN2: -22,
          subN3: 23,
          subA2: [5, 5, 5],
          subA3: [31, 32, 33],
          subB2: false,
          subB3: true,
          subU2: 'not null --- ',
          subU3: null,
          subD2: ' not undefined ----',
          subD3: undefined,
        },
        propO3: {
          subS1: 'sub31',
          subS2: 'sub32',
          subN1: 31,
          subN2: 32,
          subA1: [31, 32, 33],
          subA2: [],
          subB1: false,
          subB2: true,
          subU1: null,
          subU2: null,
          subD1: undefined,
          subD2: undefined,
        },
      };
      dest = recursivelyMoveProperties(source, dest);

      expect(dest).toEqual({
        propS1: 'str1',
        propS2: 'str2',
        propS3: 'str3',
        propN1: 1,
        propN2: 2,
        propN3: 3,
        propA1: [1, 2, 3],
        propA2: [],
        propA3: [3, 2, 1],
        propB1: true,
        propB2: false,
        propB3: false,
        propU1: null,
        propU2: null,
        propU3: null,
        propD1: undefined,
        propD2: undefined,
        propD3: undefined,
        propO1: {
          subS1: 'sub11',
          subS2: 'sub12',
          subN1: 11,
          subN2: 12,
          subA1: [11, 12, 13],
          subA2: [],
          subB1: false,
          subB2: true,
          subU1: null,
          subU2: null,
          subD1: undefined,
          subD2: undefined,
        },
        propO2: {
          subS1: 'sub21',
          subS2: 'sub22',
          subS3: 'sub23',
          subN1: 21,
          subN2: 22,
          subN3: 23,
          subA1: [21, 22, 23],
          subA2: [],
          subA3: [31, 32, 33],
          subB1: false,
          subB2: true,
          subB3: true,
          subU1: null,
          subU2: null,
          subU3: null,
          subD1: undefined,
          subD2: undefined,
          subD3: undefined,
        },
        propO3: {
          subS1: 'sub31',
          subS2: 'sub32',
          subN1: 31,
          subN2: 32,
          subA1: [31, 32, 33],
          subA2: [],
          subB1: false,
          subB2: true,
          subU1: null,
          subU2: null,
          subD1: undefined,
          subD2: undefined,
        },
      });
    });
  });

사용 예: 기본 구성 병합

다음과 같은 형식으로 설정을 정의하는 경우:

const defaultConf = {
    prop1: 'config1',
    prop2: 'config2'
}

다음의 조작에 의해서, 보다 구체적인 설정을 정의할 수 있습니다.

const moreSpecificConf = {
    ...defaultConf,
    prop3: 'config3'
}

단, 이들 설정에 네스트된 구조가 포함되어 있는 경우 이 접근법은 기능하지 않습니다.

저는 을 합치는 을 '합치다'의.{ key: value, ... }나머지를 대신할 수 있습니다.

const isObject = (val) => val === Object(val);

const merge = (...objects) =>
    objects.reduce(
        (obj1, obj2) => ({
            ...obj1,
            ...obj2,
            ...Object.keys(obj2)
                .filter((key) => key in obj1 && isObject(obj1[key]) && isObject(obj2[key]))
                .map((key) => ({[key]: merge(obj1[key], obj2[key])}))
                .reduce((n1, n2) => ({...n1, ...n2}), {})
        }),
        {}
    );

재귀를 사용한 또 다른 변형입니다. 유용하게 쓰이시길 바랍니다.

const merge = (obj1, obj2) => {

    const recursiveMerge = (obj, entries) => {
         for (const [key, value] of entries) {
            if (typeof value === "object") {
               obj[key] = obj[key] ? {...obj[key]} : {};
               recursiveMerge(obj[key], Object.entries(value))
            else {
               obj[key] = value;
            }
          }

          return obj;
    }

    return recursiveMerge(obj1, Object.entries(obj2))
}

이 사용 사례는 기본값을 구성에 병합하는 것이었습니다.내 컴포넌트가 깊이 중첩된 구조를 가진 구성 개체를 받아들이고 내 컴포넌트가 기본 구성을 정의하는 경우 제공되지 않은 모든 구성 옵션에 대해 구성 기본값을 설정하려고 합니다.

사용 예:

export default MyComponent = ({config}) => {
  const mergedConfig = mergeDefaults(config, {header:{margins:{left:10, top: 10}}});
  // Component code here
}

이를 통해 빈 Configuration 또는 Null Configuration 또는 일부 Configuration을 전달하여 설정되지 않은 모든 값을 기본값으로 폴백할 수 있습니다.

★★★의 mergeDefaults음음음같 뭇매하다

export default function mergeDefaults(config, defaults) {
  if (config === null || config === undefined) return defaults;
  for (var attrname in defaults) {
    if (defaults[attrname].constructor === Object) config[attrname] = mergeDefaults(config[attrname], defaults[attrname]);
    else if (config[attrname] === undefined) config[attrname] = defaults[attrname];
  }
  return config;
}


그리고 이건 내 유닛 테스트야

import '@testing-library/jest-dom/extend-expect';
import mergeDefaults from './mergeDefaults';

describe('mergeDefaults', () => {
  it('should create configuration', () => {
    const config = mergeDefaults(null, { a: 10, b: { c: 'default1', d: 'default2' } });
    expect(config.a).toStrictEqual(10);
    expect(config.b.c).toStrictEqual('default1');
    expect(config.b.d).toStrictEqual('default2');
  });
  it('should fill configuration', () => {
    const config = mergeDefaults({}, { a: 10, b: { c: 'default1', d: 'default2' } });
    expect(config.a).toStrictEqual(10);
    expect(config.b.c).toStrictEqual('default1');
    expect(config.b.d).toStrictEqual('default2');
  });
  it('should not overwrite configuration', () => {
    const config = mergeDefaults({ a: 12, b: { c: 'config1', d: 'config2' } }, { a: 10, b: { c: 'default1', d: 'default2' } });
    expect(config.a).toStrictEqual(12);
    expect(config.b.c).toStrictEqual('config1');
    expect(config.b.d).toStrictEqual('config2');
  });
  it('should merge configuration', () => {
    const config = mergeDefaults({ a: 12, b: { d: 'config2' } }, { a: 10, b: { c: 'default1', d: 'default2' }, e: 15 });
    expect(config.a).toStrictEqual(12);
    expect(config.b.c).toStrictEqual('default1');
    expect(config.b.d).toStrictEqual('config2');
    expect(config.e).toStrictEqual(15);
  });
});

나는 기존의 어떤 해결책도 마음에 들지 않았다.그래서 제가 직접 썼죠.

Object.prototype.merge = function(object) {
    for (const key in object) {
        if (object.hasOwnProperty(key)) {
            if (typeof this[key] === "object" && typeof object[key] === "object") {
                this[key].merge(object[key]);

                continue;
            }

            this[key] = object[key];
        }
    }

    return this;
}

이것이 무슨 일이 일어나고 있는지 이해하려고 애쓰는 여러분들에게 도움이 되기를 바랍니다.여기서 의미없는 변수들이 많이 사용되고 있는 걸 봤어요.

감사해요.

언급URL : https://stackoverflow.com/questions/27936772/how-to-deep-merge-instead-of-shallow-merge

반응형