본문으로 건너뛰기
jsonbeautifiers
한국어

JSON 비교

구조로 비교하기 때문에 서식 변경이나 키 순서는 차이로 잡히지 않습니다.

원본
변경본

붙여 넣은 것은 여러분의 브라우저를 떠나지 않습니다. connect-src 허용 목록 덕분에 이는 약속이 아니라 브라우저가 강제하는 보장입니다. 직접 확인하기

이 도구는 두 문서를 줄 단위가 아니라 구조로 비교합니다. 한쪽을 다시 정렬하든, 키를 정렬하든, 들여쓰기를 바꾸든 diff는 비어 있습니다. 그 어느 것도 데이터를 바꾸지 않았기 때문입니다.

JSON에 대한 텍스트 diff가 거의 쓸모없는 이유가 바로 그것입니다. 다른 라이브러리로 다시 직렬화하면 실제로는 아무것도 바뀌지 않았는데 모든 줄이 변경으로 잡힙니다.

무엇을 차이로 볼 것인가

네 가지 변경 유형이 있고, 각각 정확한 위치를 가리키는 JSON Pointer와 함께 보고됩니다.

추가됨
오른쪽에는 있고 왼쪽에는 없는 키나 요소.
삭제됨
왼쪽에는 있고 오른쪽에는 없는 것.
변경됨
양쪽에 있고 같은 타입이면서 값이 다른 것.
타입 바뀜
양쪽에 있지만 타입이 다른 것. 따로 볼 값어치가 있습니다. 숫자가 문자열이 된 것은 대개 데이터 변경이 아니라 직렬화 버그이기 때문입니다.

배열은 어떻게 비교하며, 왜 중요한가

배열은 모든 구조적 diff에서 어려운 부분이고, 정답이 하나만 있는 것도 아닙니다.

위치별 비교는 형태가 고정된 튜플에는 맞고 리스트에는 재앙입니다. 1,000개짜리 배열 앞에 하나를 끼워 넣으면 그 뒤 모든 위치가 변경으로 잡히고, 진짜 차이 하나가 가짜 차이 1,000개에 묻힙니다.

이 도구는 대신 최장 공통 부분수열 방식을 씁니다. 그래서 삽입은 삽입으로 보고됩니다. 그 비용은 두 길이의 곱에 비례하므로, 임계값을 넘으면 위치별 비교로 물러나고 그 사실을 노트에 밝힙니다. 조용히 품질이 떨어지는 일은 없습니다.

일부러 차이로 보지 않는 것

키 순서
명세상 JSON 객체는 순서가 없습니다. 같은 멤버가 다른 순서로 들어 있는 두 문서는 같은 문서입니다.
공백과 들여쓰기
파서에게 의미가 없으므로 여기서도 보이지 않습니다.
0과 -0
Object.is로는 다르지만 JSON 숫자로는 동일합니다.

How to do this in code

코드에서의 구조 비교.

sh jq

기억할 것은 첫 줄입니다. 키를 먼저 정렬하면 텍스트 diff의 잡음이 대부분 사라집니다.

# Sort keys recursively, then diff the text. Cheap and
# surprisingly effective for small documents.
diff <(jq -S . a.json) <(jq -S . b.json)

# Keys present in one and not the other
jq -n --slurpfile a a.json --slurpfile b b.json \
  '($a[0] | paths) - ($b[0] | paths)'
py Python
from deepdiff import DeepDiff

diff = DeepDiff(a, b, ignore_order=True)
print(diff)

# Standard library only, for a shallow comparison
changed = {k for k in a.keys() | b.keys() if a.get(k) != b.get(k)}
js JavaScript

JSON Patch는 JSON diff를 주고받는 표준 형식이고, 그 경로는 이 도구가 보고하는 것과 같은 RFC 6901 포인터입니다.

import { compare } from 'fast-json-patch';

// RFC 6902 JSON Patch: a list of operations that turns a into b
const patch = compare(a, b);
// [{ op: 'replace', path: '/user/name', value: 'Priya' }]
go Go

go-cmp는 Go 테스트에서의 표준 선택이며 중첩 구조도 읽기 좋게 출력합니다.

import "github.com/google/go-cmp/cmp"

if d := cmp.Diff(a, b); d != "" {
    t.Errorf("mismatch (-want +got):\n%s", d)
}

자주 묻는 질문

키 순서를 바꿨는데 왜 차이가 안 나오나요?
RFC 8259가 객체를 순서 없는 컬렉션으로 정의하기 때문입니다. 실제로는 모든 파서가 삽입 순서를 유지하지만, 그것에 기대는 diff는 데이터가 아니라 직렬화를 시험하는 셈입니다.
타임스탬프 같은 필드를 무시할 수 있나요?
있습니다. 경로를 무시하면 비교에서 완전히 빠집니다. requestId와 generatedAt만 다른 두 API 응답을 비교할 때 쓰는 방법입니다.
JSON Pointer가 무엇인가요?
RFC 6901입니다. /users/0/email처럼 슬래시로 구분한 경로입니다. 키 안에서 이스케이프가 필요한 문자는 둘뿐으로, ~는 ~0이 되고 /는 ~1이 됩니다. 여기서 보고되는 모든 변경에 하나씩 붙으므로 위치를 프로그램으로 지정할 수 있습니다.