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이 됩니다. 여기서 보고되는 모든 변경에 하나씩 붙으므로 위치를 프로그램으로 지정할 수 있습니다.