파싱 성공 메시지 뒤에 원래 값 하나가 사라질 수 있습니다

JSON을 검사했더니 오류가 없는데 상태값이 예상과 다르다면, 같은 객체 안에 같은 키가 두 번 있는지 확인하세요. 객체를 사전으로 바꾸는 과정에서 앞의 값이 뒤의 값으로 대체되면, 검사 뒤의 결과만 봐서는 원래 값이 두 개였다는 사실을 알기 어렵습니다. 스키마가 status라는 필드 하나와 문자열 값 하나를 요구한다면 이미 접힌 결과는 그 조건도 통과할 수 있습니다. 그래서 중복 키 검사는 보통 사전으로 변환한 다음보다 원래 멤버 쌍이 남아 있을 때 필요합니다.

RFC 8259는 객체 이름의 고유성을 권고하고, 중복 이름을 만난 구현들의 동작이 같지 않을 수 있음을 설명합니다. Python 기본 json 파서는 같은 이름이 반복되면 마지막 값만 남깁니다. 따라서 이 글은 모든 파서가 같은 방식으로 실패한다고 주장하지 않습니다. 하나의 실행 환경에서 값이 사라지는 동작을 재현하고, 우리 쪽 가져오기 정책으로 중복을 보류하는 방법을 만들었습니다. 더 엄격한 I-JSON 규칙도 객체 멤버 이름의 중복을 금지합니다.

이번 실험은 가상 작업 객체와 짧은 문자열 다섯 개를 사용했습니다. 실제 API 서버의 응답이나 AI 모델 출력은 수집하지 않았습니다. 확인할 것은 파서가 성공했느냐가 아니라, 변환 전에 존재한 이름과 값의 관계가 어떻게 달라지느냐입니다. 입력 문자열과 기본 파서 결과, 별도 검출기의 판정을 함께 공개해 같은 차이를 직접 대조할 수 있게 했습니다.

상태와 통화가 각각 두 번 있는 객체를 만들었습니다

첫 사례에는 작업 J-91, queued와 done이라는 두 status 값, KRW와 USD라는 두 currency 값을 넣었습니다. 통화는 meta라는 중첩 객체 안에 있습니다. 기본 파서는 최종 상태를 done, 최종 통화를 USD로 남겼습니다. 결과만 보면 각각 하나의 필드가 있는 정상적인 객체입니다. 앞에 있던 queued와 KRW를 나중에 사전에서 찾아보려고 해도 이미 사라졌습니다. 처리 전에 원문을 보존하지 않으면 입력의 모호함을 설명할 단서도 함께 잃습니다.

{"job":"J-91","status":"queued","status":"done","meta":{"currency":"KRW","currency":"USD"}}

별도 검출기는 중복 위치로 /status와 /meta/currency를 기록하고 이 입력을 정상 출력으로 내보내지 않았습니다. 값이 서로 다르기 때문에 더 위험해 보이지만, 같은 값이 반복돼도 이 글의 고유성 계약에서는 보류해야 합니다. 지금 두 값이 같다는 이유로 중복 구조를 허용하면 공급자의 오류가 계속 남을 수 있고, 다음 입력에서 값이 달라졌을 때 정책이 갑자기 바뀌기 때문입니다. 어떤 중복까지 허용할지는 자료 계약에서 정해야 합니다.

여기서 보류는 뒤의 값이 틀렸다는 판정이 아닙니다. 앞의 값이 최신일 수도, 뒤의 값이 최신일 수도 있지만 입력만으로 그 우선순위를 알 수 없습니다. 파서의 마지막 값 유지 규칙을 업무의 최신 상태 판단과 같게 취급하지 않는 것이 중요합니다. 시간 정보나 버전 계약이 필요한 문제를 텍스트의 등장 순서만으로 해결하지 않습니다.

사전으로 접기 전에 이름·값 쌍을 유지합니다

Python의 object_pairs_hook을 사용하면 객체를 일반 사전으로 바로 만들지 않고 순서가 있는 이름·값 쌍으로 받을 수 있습니다. 실험 코드는 이를 전용 목록 형태로 보관한 뒤, 객체별로 이미 본 이름을 검사합니다. 배열과 객체를 같은 자료형으로 착각하지 않도록 별도 표시를 둔 점도 중요합니다. 배열 안에 있는 두 객체가 각각 id라는 이름을 갖는 것은 같은 객체 안의 중복과 다릅니다. 검사의 범위를 객체마다 새로 잡아야 이런 정상 구조를 막지 않습니다.

중복이 없을 때만 최종 사전 값을 정상 결과로 돌려줍니다. 중복이 발견됐을 때 계산 중 만들어진 마지막 값 사전을 경고와 함께 정상 출력으로 내보내지는 않습니다. 경고 목록을 놓친 다음 단계가 그 값을 다시 사용하면 보류 정책이 사실상 무력해지기 때문입니다. 다운로드 결과의 기본 파서 값은 비교 설명용으로 따로 둔 값이며, 승인된 값이라는 뜻이 아닙니다. 검출 결과와 처리 가능한 결과를 필드 수준에서 구분했습니다.

이 접근법은 원문 바이트의 모든 위치를 제공하는 파서를 만든 것은 아닙니다. 이름·값 쌍의 구조와 순서를 유지하지만 공백, 들여쓰기, 원래 이스케이프 표기까지 그대로 보존하는 도구는 아닙니다. 원본의 정확한 바이트나 줄·열 번호가 필요하면 원문도 별도로 보관하고, 그런 위치 정보를 제공하는 파서를 검토해야 합니다. 이번 검출기는 중복 여부와 구조상 경로를 확인하는 데 범위를 뒀습니다.

겉모양보다 JSON이 해석한 키의 값을 비교합니다

두 번째 사례는 a라는 키와 유니코드 이스케이프로 같은 문자를 나타내는 키를 함께 넣었습니다. 원문 텍스트에는 다른 표기가 쓰였지만 JSON 문자열을 해석하면 둘 다 a입니다. 이 사례도 /a에서 중복으로 잡혔습니다. 따라서 원문에서 따옴표 사이의 글자를 정규식으로 찾아 그대로 비교하는 방법만으로는 중복을 놓칠 수 있습니다. 문자열의 이스케이프와 중첩 구조를 파서가 먼저 이해한 뒤 이름의 값을 검사해야 합니다.

반대로 악센트가 붙은 é 한 글자와 e 뒤에 결합 악센트를 붙인 문자열은 이 실험에서 서로 다른 키로 남았습니다. 눈으로 비슷하게 보인다는 이유만으로 Unicode 정규화를 자동 적용하지 않았기 때문입니다. 자료 계약에서 정규화된 이름만 허용하려면 별도 검사가 필요합니다. 다만 중복 검출기가 임의로 정규화해 이름을 합쳐 버리면 원래 서로 다른 키를 바꾸는 동작이 되므로, 정규화 정책도 명시적으로 구분해야 합니다.

대소문자 역시 JSON 객체 이름에서는 자동으로 같은 이름이 되지 않습니다. 사용 시스템이 필드 이름을 소문자로 바꾸어 처리한다면 JSON 수준의 고유성 검사 뒤에 그 시스템의 충돌 검사가 추가로 필요할 수 있습니다. 이 실험에서 정상이라고 판정한 이름들이 이후 모든 도구에서도 충돌하지 않는다는 뜻은 아닙니다. 어떤 단계에서 이름이 변형되는지 알아야 검사 범위가 맞습니다.

오류 위치를 경로로 적되 한 번의 등장 위치로 오해하지 않습니다

중첩 객체에서는 키 이름만 적으면 어느 위치를 고쳐야 하는지 알기 어렵습니다. 실험은 JSON Pointer 방식의 경로를 사용해 상태를 /status, 중첩 통화를 /meta/currency처럼 나타냈습니다. 배열 안의 객체라면 배열 인덱스도 경로에 포함됩니다. 따라서 서로 다른 객체 안에서 같은 이름이 쓰였는지, 한 객체에서 반복됐는지를 사람이 확인하기가 쉽습니다. 원문 전체를 오류 메시지에 복사하는 것보다 위치를 짧게 전달할 수 있다는 장점도 있습니다.

키 자체에 슬래시가 있는 다섯 번째 사례에서는 경로를 /a~1b로 기록했습니다. 슬래시와 물결표를 그대로 붙이면 경로 구조와 실제 이름이 섞이므로 RFC 6901의 이스케이프 규칙을 적용합니다. 이 경로는 중복된 이름이 속한 자리를 설명할 뿐, 원문에서 첫 번째와 두 번째 등장 각각의 바이트 위치를 구별해 주지는 않습니다. 중복 키를 보류하는 상황에서는 하나의 경로가 유일한 값 하나를 가리킨다고 가정하지 않는 편이 정확합니다.

운영 원장에는 원본 식별자와 경로, 중복 발견 순서, 필요하다면 원문 위치 정보를 나눠 넣을 수 있습니다. 실제 민감한 자료를 다룬다면 두 값 전체를 공개 로그에 남기기보다 제한된 원본 저장소와 연결하세요. 이름과 경로 자체에도 고객 식별 정보가 들어갈 수 있으므로, 값만 가리면 로그가 익명이라는 가정은 피해야 합니다.

배열 경로의 숫자는 원문 줄 번호와도 다릅니다. 코드에서는 배열의 첫 요소를 0으로 표현하므로 /0/id는 첫 객체의 id를 가리키는 구조 표기입니다. 줄 번호를 함께 제공한다면 두 기준을 열 이름에서 구분하세요. 들여쓰기를 바꾸면 줄은 이동할 수 있지만 객체 경로의 의미는 유지될 수 있고, 배열 순서를 바꾸면 그 인덱스도 달라집니다.

다섯 사례에서 세 개를 보류하고 두 개를 통과시켰습니다

실행 결과 중첩 중복, 이스케이프 이름 중복, 슬래시 이름 중복의 세 입력은 보류됐습니다. 서로 다른 객체가 id를 갖는 배열과 정규화를 하지 않은 두 다른 문자열 키 사례는 통과했습니다. 이 판정표는 단순히 더 많이 막는 검사를 만들려는 것이 아닙니다. 정상적인 반복 구조와 같은 객체 안의 충돌을 구별하는지 확인한 것입니다. 자료를 충분히 구분하지 못하고 모든 id라는 단어의 반복을 막으면 정상 배열도 사용할 수 없게 됩니다.

사례판정이유
중첩 status·currency보류각 객체 안에서 이름 반복
a와 이스케이프 a보류해석된 문자열이 동일
배열의 서로 다른 두 객체통과객체별 이름은 고유
é와 결합 문자 표현통과정규화하지 않은 문자열은 다름
a/b의 반복보류경로에는 ~1로 이스케이프

실제 업무에 맞추려면 동일 값 반복, 빈 키, 큰 객체, 여러 배열 단계도 고정 입력으로 더할 수 있습니다. 다만 사례 수가 많다는 사실보다 기대 판정이 명확하고 실제 결과와 대조되는지가 중요합니다. 이번 다섯 개가 모든 JSON 파서의 성능이나 모든 잘못된 문서를 평가하는 기준은 아닙니다. 경계별로 무엇을 확인했는지 결과와 함께 읽어야 합니다.

중복 키 검사는 스키마 검사보다 앞의 정보 보존 단계입니다

중복 없는 객체를 얻은 다음에는 필수 필드, 자료형, 날짜와 상태값을 검사할 수 있습니다. 앞단에서 하나의 status로 접혀 버린 값을 스키마가 복구해 주지는 않습니다. 처리 순서를 원문 보존, 파싱 중 중복 검사, 스키마 검사, 업무 상태 판정으로 나누면 각 검사가 담당하는 일이 드러납니다. 어느 한 단계의 통과가 나머지의 통과를 대신하지 않습니다. 특히 문법이 성공했다는 메시지를 데이터 승인 화면의 녹색 표시 하나로 연결하지 않는 것이 좋습니다.

문법과 작업 계약을 나눠 보는 JSON 가이드는 이미 읽힌 결과의 자료형과 필드를 다룹니다. 이름 변경이나 필드 추가로 계약을 바꾼다면 명시적 버전 이관과 역호환 검사로 이어집니다. 이번 중복 검출은 그 이전에 입력의 모호함을 보류하는 역할입니다. 비슷한 단어를 사용하더라도 검사 지점과 보존해야 할 증거가 다릅니다.

공급자에게는 단순히 JSON 오류라고만 알리기보다 어떤 객체 경로의 이름이 반복됐는지 전달하세요. 생성 도구가 객체를 조립하는 과정에서 같은 필드를 두 번 더했는지, 병합 과정의 우선순위가 정의되지 않았는지 조사할 수 있습니다. 한쪽 값을 자동 선택하는 임시 대응을 하더라도 그 정책을 정상 입력 계약처럼 숨기지 않아야 다음 버전에서 다시 검토할 수 있습니다.

키가 고유해도 값의 사실성까지 확인되는 것은 아닙니다

이 코드는 고정한 JSON 문자열에서 해석된 키의 고유성을 검사합니다. 숫자의 정밀도, 최대 중첩 깊이, 서비스의 권한 검사, 실제 상태값의 진위를 모두 확인하는 도구가 아닙니다. 매우 큰 외부 입력을 처리하려면 크기와 깊이 제한, 시간 제한과 오류 원장 정책도 필요합니다. 이번 자료는 작은 가상 입력을 메모리에서 처리했으므로 그런 운영 환경의 자원 한계를 실측한 결과는 없습니다. 코드가 끝났다는 사실을 모든 외부 JSON의 안전성으로 확대하지 않습니다.

직접 확인할 때는 첫 사례의 기본 파서 결과와 보류 판정을 나란히 보세요. 앞의 값이 어디에서 사라졌는지, 검출기가 정상 출력으로 무엇을 내보내지 않는지 확인할 수 있습니다. 이후 자신의 계약에서 키 변형이나 정규화가 있는지 적고, 그 단계의 충돌을 따로 검사하면 됩니다. 사라진 값을 나중에 추측하기보다 사라지기 전에 모호함을 드러내는 것이 이 검수의 목적입니다.

입력과 결과를 직접 대조하는 재현 자료

실험 실행일은 2026년 10월 15일이며 이 글의 발행 일정과 구분합니다. Python 3 표준 라이브러리로 가상 자료만 처리했습니다. 아래 파일은 기존 사용자 파일을 인자로 받지 않습니다.

python3 json-duplicate-key-detection-lab-20261015.py

실행 시각과 런타임은 환경에 따라 달라집니다. 판정 근거는 다운로드 결과의 고정 입력과 각 사례의 실제 출력으로 확인하세요.

규칙을 확인한 공식 원문

아래 원문은 2026년 10월 15일 확인했습니다. 형식과 동작의 설명을 확인한 출처이며, 이 글의 가상 실험이나 실제 업무 성과를 대신 증명하지 않습니다.

  • RFC 8259: Objects — 객체 이름의 고유성 권고와 중복 이름의 상호운용성 문제를 확인했습니다.
  • Python json: Repeated Names — 기본 파서의 마지막 값 유지와 object_pairs_hook 동작을 확인했습니다.
  • RFC 7493: I-JSON — I-JSON이 객체의 중복 멤버 이름을 금지하는 더 엄격한 규칙임을 확인했습니다.
  • RFC6901: JSON Pointer — 오류 위치의 ~0·~1 이스케이프 표기를 확인했습니다.