API 응답, 설정 파일, 번역 파일처럼 JSON 으로 된 데이터를 비교할 일은 자주 생깁니다. 그런데 두 JSON 을 그대로 텍스트 비교하면 데이터는 같은데도 차이가 잔뜩 나오는 경우가 많습니다. 이 글은 그 이유와 해결 방법을 설명합니다.
왜 가짜 차이가 생기나
JSON 표준(RFC 8259)은 객체를 "순서 없는 이름/값 쌍의 모음"으로 정의합니다. 즉 {"a":1,"b":2} 와 {"b":2,"a":1} 은 같은 데이터를 뜻합니다. 하지만 텍스트로는 줄의 순서가 다르므로 diff 는 차이로 봅니다. 그 밖에도 다음 차이가 데이터와 무관하게 생깁니다.
- 들여쓰기 폭(2칸, 4칸, 탭)과 한 줄로 압축된 JSON
- 콜론 뒤 공백 유무(
"a":1과"a": 1) - 숫자 표기(
1.0과1,1e2와100) - 문자열 이스케이프(
"\u00e9"와"é")
해결: 파싱한 뒤 같은 모양으로 다시 쓰기
가장 확실한 방법은 두 JSON 을 파싱해서 데이터로 만든 다음, 같은 규칙으로 다시 직렬화하는 것입니다.
- 두 텍스트를 JSON 으로 파싱합니다. 여기서 문법 오류가 있으면 먼저 고칩니다.
- 모든 객체의 키를 재귀적으로 정렬합니다.
- 같은 들여쓰기(예: 2칸)로 다시 씁니다.
- 그 결과를 줄 단위로 비교합니다.
예를 들어 다음 두 JSON 은 키 순서만 다르고 배열 순서가 다릅니다.
{"name":"kim","age":30,"tags":["a","b"]}
{"age":30,"name":"kim","tags":["b","a"]}
정렬하고 다시 쓰면 키 순서 차이는 사라지고 진짜 차이인 배열 순서만 남습니다.
@@ -2,7 +2,7 @@ "age": 30, "name": "kim", "tags": [- "a",- "b"+ "b",+ "a" ] }
배열 순서는 정렬하지 않는다
객체와 달리 배열은 순서가 의미를 가집니다. ["a","b"] 와 ["b","a"] 는 다른 데이터입니다. 그래서 정규화할 때 배열은 정렬하지 않는 것이 원칙입니다. 다만 태그 목록처럼 업무상 순서가 중요하지 않은 배열이라면, 비교 전에 직접 정렬하는 편이 결과를 읽기 쉽게 만듭니다. 어느 쪽이 맞는지는 데이터의 의미에 달려 있습니다.
파싱이 바꿔 버리는 것들
파싱 후 다시 쓰는 방법에는 알아 둘 부작용이 있습니다.
- 숫자 표기가 통일됩니다.
1.0과1은 모두1이 되어 같게 보입니다. 대부분은 바람직하지만, 표기 자체가 중요한 경우에는 원문 비교도 해 보세요. - 큰 정수의 정밀도. 자바스크립트는 숫자를 64비트 부동소수점으로 다루므로
Number.MAX_SAFE_INTEGER(2^53 − 1 = 9007199254740991)보다 큰 정수는 정확히 표현하지 못합니다. 예를 들어9007199254740993은 파싱하면9007199254740992가 됩니다. 긴 ID 가 숫자로 들어 있는 JSON 은 이 점에 주의하세요. - 중복된 키. RFC 8259 는 객체 안의 이름이 고유해야 한다고 권고(SHOULD)만 하며, 중복 키가 있을 때 어떻게 처리할지는 구현마다 다릅니다. 자바스크립트의
JSON.parse는 마지막 값을 남깁니다. - 주석과 끝 쉼표.
// 주석이나[1, 2,]같은 끝 쉼표는 표준 JSON 이 아닙니다(JSON5, JSONC 같은 확장 형식에서 허용). 표준 파서는 오류를 냅니다.
명령행에서 하는 방법
jq 가 설치돼 있다면 -S(--sort-keys) 옵션으로 키를 정렬해 출력할 수 있습니다.
jq -S . before.json > a.json
jq -S . after.json > b.json
diff -u a.json b.json
JSON Patch 와의 차이
diff 결과는 사람이 읽기 위한 줄 단위 변경 목록입니다. 반면 JSON Patch(RFC 6902)는 {"op":"replace","path":"/age","value":31} 처럼 JSON 구조의 경로로 변경을 표현하는 표준이고, JSON Merge Patch(RFC 7396)는 바뀐 부분만 담은 JSON 을 원본에 덮어쓰는 방식입니다. 프로그램끼리 변경을 주고받는다면 이 표준들이, 사람이 검토한다면 정렬 후 텍스트 diff 가 알맞습니다.
이 도구에서 해 보기
텍스트 비교기의 옵션에서 JSON 키 정렬을 켜면, 양쪽을 파싱해 키를 정렬하고 2칸 들여쓰기로 다시 쓴 다음 비교합니다. 배열 순서는 바꾸지 않으며, 파싱에 실패하면 어느 쪽 몇 번째 글자에서 오류가 났는지 알려 줍니다. .json 파일 두 개를 한꺼번에 끌어다 놓아도 됩니다. 결과를 patch 로 읽는 법은 unified diff 읽는 법을 참고하세요.