객체 비교에 JSON.stringify() 를 사용해서는 안됩니다. — 속성에는 순서가 없습니다.

우연히 움직이는 코드



객체끼리의 비교에 JSON.stringify() 를 사용하는 예가 여기저기서 보입니다.
일반적으로
JSON.stringify(objA) === JSON.stringify(objB)

같은 코드입니다.
오브젝트의 내용을 재귀적으로 따라 비교하는, 이른바 「깊은 비교」로 「deep equality」를 판정하고 싶을 경우에 사용되는 일이 많은 것 같습니다만, 이것은 매우 위험해, 언제 망가져도 이상하지 않은 코드입니다.

이유



우선 JSON화할 때 그대로 포함되지 않는 프로퍼티(enumerable가 아니거나, 값이 함수이거나 하는 것 등)이 있습니다만, 이것은 고려상에서의 일이 많을 것입니다
문제는 배열이 아닌 자바스크립트의 객체에는 순서가 없고, JSON이 될 때에도 속성의 순서가 어떻게 될지 결정되지 않는다는 것입니다.
MDN의 경우

배열이 아닌 객체의 속성은 특정 순서로 문자열화되도록 보장되지 않습니다. 문자열화에서 동일한 객체의 속성 순서를 믿지 마십시오.
JSON.stringify({ x: 5, y: 6 });
// '{"x":5,"y":6}' または '{"y":6,"x":5}'

그리고 있습니다.
이것은, 같은 값의 동명의 프로퍼티 밖에 가지지 않는 오브젝트라도, 다른 캐릭터 라인으로 변환될 수 있다, 라고 하는 것입니다.
생성된 JSON끼리를 문자열로서 비교해, 객체의 비교에 대신할 수 없게 됩니다.

실제 예



예를 들어,
let objA = {}
objA.x = 0
objA.y = 0

let objB = {}
objB.y = 0
objB.x = 0

console.log( JSON.stringify(objA) === JSON.stringify(objB) )

생각합니다.objAobjB 는 JSON화의 대상이 되는 프로퍼티, xy 를 가지고 있습니다.
각각 값도 같기 때문에 이것은 비교 결과로서 true (같음)을 기대하고 있습니다.
그러나 Node.js v6.11.4의 결과는 false (같지 않음)입니다.
생성된 JSON은
// objA
'{"x":0,"y":0}'

// objB
'{"y":0,"x":0}'

되어 있습니다.
이 동작은 다음과 같이 설명할 수 있다고 생각합니다.
  • objAobjB 에서는 속성을 추가한 순서가 다르다
  • 현재의 Node.js에서는 for..in 등으로의 프롭퍼티의 열거가, 프롭퍼티를 추가한 순서로 행해진다
  • 현재의 Node.js에서는 JSON.stringify() 로 JSON화 하면(자), 프로퍼티이 열거되는 순서로 출현한다

  • 그러나 이것은 어디 까지나, 우연히이 버전의 Node.js에서는 이렇게 되어 있었다, 라는 이야기 밖에 없다는 것에 주의해 주세요.

    속성을 추가하는 순서에 따라 달라지면 같은 순서로 추가하는 것에주의하십시오.

    라고 생각하면 위험합니다.
    장래는 동작이 바뀔지도 모릅니다(프로퍼티를 추가하는 순서에 의존하는 코드를 장래에 걸쳐 메인트 한다는 것도 조금 귀찮네요).

    대안



    그럼 깊은 비교를 하고 싶을 때는 어떻게 하면 좋을까 하면, 유감스럽지만 간단한 방법은 없습니다.
    사용 가능한 것으로,
  • Lodash _.isEqual()
  • Node.js의 assert.deepEqual()
  • deep-equal

  • 등이 있으므로 참고하십시오.
    오브젝트끼리의 비교 방법에 결정은 없기 때문에, 같은 deep equal 를 자칭하고 있어도 사양은 조금 다른 일이 있습니다.
    JSON화에 있어서 속성의 순서를 항상 일정하게 하는 접근법도 있습니다.
  • json-stable-stringify

  • 실제 곳, 정말 깊은 비교가 필요한 것은 적기 때문에, 거기를 검토해 보는 것도 좋을 것입니다.

    이 기사의 라이센스




    이 문서는 CC BY 4.0(크리에이티브 커먼즈 표시 4.0 국제 라이센스)에 게시됩니다.

    좋은 웹페이지 즐겨찾기