HTTP API 가 리소스의 일부 만 업데이트해야 할 때, 두 IETF 표준이 그 일을 두고 경쟁합니다: JSON Merge Patch(RFC 7396) 와 JSON Patch(RFC 6902). 비슷해 보이지만 동작은 매우 다릅니다 —— Merge Patch 는 부분 객체를 덮어쓰고, JSON Patch 는 명시적인 작업 목록을 보냅니다. 이 가이드는 각각의 동작, 정면 비교, 그리고 어느 쪽을 고를지를 보여줍니다.
문제: 부분 업데이트
PUT 요청은 리소스 전체를 교체해서, 한 필드만 바꾸고 싶을 때는 낭비이고 위험합니다. HTTP PATCH 메서드가 부분 업데이트를 위해 존재하지만 —— PATCH 는 body 형식을 정의하지 않습니다. 이 두 표준이 그것을 제공합니다.
JSON Merge Patch (RFC 7396)
Merge Patch 는 대상 문서의 부분 버전입니다. 서버가 그걸 덮어씁니다: 패치의 키는 설정되고, null 로 설정된 키는 삭제 되며, 언급되지 않은 키는 그대로.
// 원본
{ "title": "Hello", "author": "Ada", "tags": ["a", "b"], "draft": true }
// Merge Patch body (Content-Type: application/merge-patch+json)
{ "title": "Hello World", "draft": null }
// 결과
{ "title": "Hello World", "author": "Ada", "tags": ["a", "b"] }
// ^ title 업데이트 ^ author 그대로 ^ draft 삭제 (null 이었음) 직관적이고 간결합니다. 함정: null 이 "삭제" 를 의미하므로 Merge Patch 로 값을 null 로 설정할 수 없습니다, 그리고 배열은 통째로만 교체 가능 —— 단일 배열 요소를 바꿀 방법이 없습니다.
JSON Patch (RFC 6902)
JSON Patch 는 순서 있는 작업 배열이고, 각 작업은 JSON Pointer 경로로 위치를 지정합니다. 명시적이고 정확:
// JSON Patch body (Content-Type: application/json-patch+json)
[
{ "op": "replace", "path": "/title", "value": "Hello World" },
{ "op": "remove", "path": "/draft" },
{ "op": "add", "path": "/tags/-", "value": "c" },
{ "op": "test", "path": "/author", "value": "Ada" }
]여섯 가지 작업:
| op | 효과 |
|---|---|
add | 값 추가 (또는 /- 로 배열 끝에 append) |
remove | 경로의 값 삭제 |
replace | 경로의 값 교체 |
move | 값을 한 경로에서 다른 경로로 이동 |
copy | 값을 다른 경로로 복사 |
test | 값을 단언 (실패 시 전체 패치 중단) |
test 작업은 안전한 원자적 업데이트를 가능하게 합니다: 문서가 예상 상태가 아니면 전체 패치가 거부됩니다 —— 낙관적 동시성 제어에 유용.
정면 비교
| JSON Merge Patch | JSON Patch | |
|---|---|---|
| RFC | 7396 | 6902 |
| Content-Type | application/merge-patch+json | application/json-patch+json |
| 가독성 | 높음 —— 데이터처럼 보임 | 낮음 —— 작업 목록 |
| 값을 null 로 설정 가능 | 불가 (null 은 삭제 의미) | 가능 |
| 단일 배열 요소 수정 | 불가 (배열 전체 교체) | 가능 (인덱스로) |
| 조건부 / 원자적 업데이트 | 불가 | 가능 (test 작업) |
| Move / copy | 불가 | 가능 |
| 최적 용도 | 간단한 객체 필드 업데이트 | 정밀한, 배열 인식의, 원자적 편집 |
어느 걸 언제 쓸지
- JSON Merge Patch —— 클라이언트가 주로 최상위 객체 필드를 업데이트하고, body 가 리소스처럼 읽히길 원할 때. 간단한 설정과 프로필 업데이트에 좋음. 다만 필드를
null로 설정하거나 배열 항목을 편집할 수 없다는 점을 기억하세요. - JSON Patch —— 배열 요소 편집, 값 이동/복사, 필드를
null로 설정, 또는 전제 조건으로 원자적 변경 적용이 필요할 때. 협업 편집과 감사 친화적 변경 집합의 표준 선택.
HTTP PATCH 시맨틱: 멱등성과 헤더
PATCH 엔드포인트를 출시하는 팀이 자주 걸리는 두 HTTP 디테일:
- PATCH 는 멱등일 필요 없음 (PUT 과 달리).
{"op":"add","path":"/items/-","value":...}가 있는 JSON Patch 는 append —— 두 번 적용하면 두 번 추가됩니다. 안전한 재시도가 필요하면test작업으로 먼저 상태를 단언하거나, 재적용이 no-op 가 되도록 작업을 설계하세요 (/items/-에add대신 고정 경로에replace). - 올바른 Content-Type 사용. RFC 6902 =
application/json-patch+json; RFC 7396 =application/merge-patch+json. 서버가 보통 이걸로 분기합니다. - 낙관적 동시성. JSON Patch 의
test작업 (또는If-MatchETag 헤더) 을 결합해 동시 쓰기 주체들이 서로를 조용히 덮어쓰지 못하게 합니다.
알아 둘 만한 라이브러리
JSON Patch 의 경우 fast-json-patch 가 사실상의 npm 패키지 —— 적용, 생성 (두 문서 diff 해서 패치 생성), 관찰 (감시 대상 객체 변화 추적) 가능. JSON Merge Patch 규칙은 인라인으로 작성할 만큼 작지만, json-merge-patch 가 null 삭제 엣지 케이스를 처리해 구현합니다.
코드에서 패치 적용
// JavaScript —— fast-json-patch 가 RFC 6902 를 구현
import { applyPatch } from 'fast-json-patch';
const updated = applyPatch(document, patchOps).newDocument;
// JSON Merge Patch 는 재귀로 적용하기 쉽지만, 라이브러리
// (예: json-merge-patch) 가 null 삭제 규칙을 정확히 처리합니다.패치 적용 후, before/after 를 diff 해서 변경이 의도한 그대로인지 확인할 수 있습니다 —— 두 JSON 파일 비교하는 법 참고, 또는 둘 다 JSON Diff 에 붙여 넣기.
자주 묻는 질문
JSON Patch 와 JSON Merge Patch 의 차이는?
JSON Merge Patch (RFC 7396) 는 문서의 부분 복사 본으로, null 이 키를 삭제. JSON Patch (RFC 6902) 는 JSON Pointer 경로를 대상으로 하는 명시적 작업 배열 (add, remove, replace, move, copy, test). Merge Patch 가 더 단순; JSON Patch 가 더 강력.
왜 JSON Merge Patch 는 필드를 null 로 설정 못 하나요?
Merge Patch 에서 null 은 키를 삭제 하라는 신호이기 때문입니다. 실제 null 값을 저장해야 한다면 JSON Patch 의 replace 작업을 쓰세요.
어떤 Content-Type 을 써야 하나요?
application/merge-patch+json 은 Merge Patch, application/json-patch+json 은 JSON Patch, 둘 다 HTTP PATCH 메서드로 전송.
배열의 한 요소를 어떻게 편집하나요?
/items/2 같은 경로로 JSON Patch 를 쓰거나 /items/- 로 append. Merge Patch 는 배열 전체만 교체할 수 있습니다.
관련 도구 & 읽을거리
- JSON Diff —— 패치가 예상한 변경을 정확히 만들었는지 검증
- 두 JSON 파일 비교하는 법 —— 시맨틱 diff 뒤의 알고리즘
- RFC 7396: JSON Merge Patch
- RFC 6901 & 6902: JSON Pointer 와 Patch