JSON Diff 디버깅 가이드: 복잡한 데이터에서 변경 찾기
표준 텍스트 diff 도구는 JSON에 잘 작동하지 않습니다. 키 재정렬, 공백 변경, 깊은 중첩이 실제 데이터 변경을 가리는 노이즈 많은 diff를 만듭니다. 구조적 JSON diff는 텍스트 표현이 아닌 의미론, 즉 실제 데이터를 비교합니다. 이 가이드에서는 효과적인 JSON 비교와 디버깅 기법을 다룹니다.
표준 Diff가 JSON에 실패하는 이유
의미론적으로 동일한 두 JSON 객체를 고려하세요:
버전 A:
{"name":"Alice","age":30,"roles":["admin","editor"]}
버전 B:
{
"age": 30,
"name": "Alice",
"roles": ["admin", "editor"]
}
텍스트 diff는 모든 줄이 변경된 것으로 표시하지만, 데이터는 동일합니다. 구조적 diff는 같다는 것을 인식합니다.
이제 실제 변경을 고려하세요:
// 이전
{ "user": { "name": "Alice", "permissions": ["read", "write", "admin"] } }
// 이후
{ "user": { "name": "Alice", "permissions": ["read", "write"] } }
구조적 diff는 보고합니다: removed user.permissions[2]: "admin" — 정확히 필요한 정보.
JSON Diff 도구로 즉시 시도해보세요.
JSON 변경 유형
구조적 JSON diff는 변경을 다음으로 분류합니다:
| 변경 유형 | 예시 |
|---|---|
| 추가됨 | 새 키 또는 배열 요소 |
| 제거됨 | 누락된 키 또는 배열 요소 |
| 수정됨 | 기존 키의 값 변경 |
| 타입 변경 | 값 타입 변경 (문자열 → 숫자) |
| 이동됨 | 배열 요소 순서 변경 |
커맨드 라인 JSON Diff
jq로 빠른 비교
# 키 정렬 후 비교
diff <(jq -S . before.json) <(jq -S . after.json)
# 특정 경로 비교
diff <(jq '.config.database' before.json) <(jq '.config.database' after.json)
전문 도구
# json-diff (npm)
npx json-diff before.json after.json
# jd (Go)
jd before.json after.json
API 응답 변경 디버깅
API 응답이 예기치 않게 변경될 때, 체계적 비교가 근본 원인을 식별하는 데 도움을 줍니다:
1단계: 기준선 캡처
알려진 정상 응답을 저장합니다:
curl -s https://api.example.com/users/123 | jq -S . > baseline.json
2단계: 현재 캡처
curl -s https://api.example.com/users/123 | jq -S . > current.json
3단계: 구조적 Diff
# 사람이 읽기 쉬운 형태
npx json-diff baseline.json current.json
# 기계가 읽기 쉬운 형태 (JSON Patch 포맷)
npx json-diff baseline.json current.json --json
4단계: 노이즈 필터링
요청마다 변경되는 필드(타임스탬프, 요청 ID)를 제외합니다:
# 비교 전 변동 필드 제거
jq 'del(.meta.requestId, .meta.timestamp)' response.json
JSON Patch (RFC 6902)
JSON Patch는 JSON 문서의 변경을 설명하는 표준화된 포맷입니다:
[
{ "op": "replace", "path": "/user/name", "value": "Bob" },
{ "op": "remove", "path": "/user/permissions/2" },
{ "op": "add", "path": "/user/email", "value": "bob@example.com" }
]
연산:
add: 새 값 추가remove: 값 제거replace: 기존 값 변경move: 새 경로로 값 이동copy: 새 경로로 값 복사test: 값의 존재 확인 (조건부 패치용)
JSON Patch는 전체 문서를 교체하는 대신 API에 점진적 업데이트를 보내는 데 유용합니다.
설정 드리프트 감지
시간에 따른 설정 파일 변경을 추적합니다:
#!/bin/bash
# drift-check.sh
BASELINE="config-baseline.json"
CURRENT="config-current.json"
DIFF=$(npx json-diff "$BASELINE" "$CURRENT" 2>/dev/null)
if [ -n "$DIFF" ]; then
echo "설정 드리프트 감지됨:"
echo "$DIFF"
exit 1
fi
echo "드리프트가 감지되지 않았습니다."
테스트에서의 JSON Diff
API 응답을 검증하기 위해 테스트에서 구조적 비교를 사용합니다:
const { diff } = require('json-diff');
test('API 응답이 예상 구조와 일치', async () => {
const response = await fetch('/api/users/123');
const data = await response.json();
const expected = {
id: 123,
name: 'Alice',
role: 'admin'
};
const changes = diff(expected, data);
expect(changes).toBeUndefined(); // undefined는 동일함을 의미
});
대용량 JSON 파일 처리
수 메가바이트의 JSON 파일에서는 시각적 diff가 비실용적입니다:
- 먼저 쿼리: diff 전에 관련 섹션을 추출하기 위해 JSONPath를 사용하세요. JSONPath 가이드를 참조하세요.
- 요약 모드: 모든 변경을 보여주는 대신 유형별 변경 수를 세세요
- 스트리밍 diff:
jd같은 도구는 스트리밍으로 대용량 파일을 효율적으로 처리합니다
# 특정 섹션을 추출하여 비교
jq '.data.users[:10]' large-before.json > section-before.json
jq '.data.users[:10]' large-after.json > section-after.json
npx json-diff section-before.json section-after.json
자주 묻는 질문
JSON Patch와 JSON Merge Patch 중 어떤 것을 사용해야 하나요?
JSON Patch (RFC 6902)는 연산 배열을 사용하며 배열 요소 조작을 포함한 모든 변경을 표현할 수 있습니다. JSON Merge Patch (RFC 7396)는 더 간단합니다 — 부분 JSON 객체를 보내면 대상과 병합됩니다. 간단한 객체 업데이트에는 Merge Patch를, 배열 조작이나 원자적 연산이 필요할 때는 JSON Patch를 사용하세요.
키 순서가 다른 JSON을 어떻게 diff하나요?
대부분의 구조적 JSON diff 도구는 키 순서를 무의미하게 취급합니다 — {"a":1,"b":2}는 {"b":2,"a":1}와 같습니다. 텍스트 기반 도구에는 jq -S .로 먼저 정규화하여 키를 알파벳순으로 정렬하세요. JSON Diff 도구는 키 순서를 자동으로 처리합니다.
관련 리소스
- JSON Diff — JSON 문서 나란히 비교
- JSON 포맷터 — 시각적 검사를 위한 JSON 포맷
- 텍스트 비교 가이드 — 일반 텍스트 비교 기법