ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • JSON에 같은 키가 두 번 있으면 어느 값이 남을까
    Data Analysis 2026. 10. 6. 00:28
    728x90
    반응형

    중복된 mode 원문이 Python 기본 파싱 후 하나로 합쳐지는 과정과 파싱 훅 검사 위치
    결과 객체에 이름이 하나 있다고 원문에도 하나였던 것은 아닙니다.

    설정 파일에 "mode": "preview"와 "mode": "normal"이 함께 있습니다. Python으로 읽으면 뒤의 값이 남지만, 그것을 모든 JSON 처리기의 규칙으로 삼으면 곤란합니다. 중복을 허용한 파서는 한 값만 남길 수도 있고, 입력을 거부하거나 이름·값 쌍을 보존할 수도 있습니다.

    중복 키의 처리 결과에 기대지 말고, 원문 단계에서 중복을 거부할지 명시한 뒤 같은 파서 정책을 적용하세요. 특히 객체로 바꾼 뒤 검사하면 이미 지워진 앞의 값을 찾을 수 없습니다.

    이 글은 2026년 9월 28일 확인한 RFC 8259와 Python 3 문서를 기준으로 입력 정책을 설명합니다. 코드는 작은 설정 문자열을 위한 예시이며 외부 요청 처리나 성능을 시험한 결과는 아닙니다.

    ‘마지막 값’은 Python 기본 디코더의 선택입니다

    RFC 8259는 한 객체 안의 이름이 유일해야 한다고 권고합니다. 표현은 SHOULD이며, 이름이 중복되면 구현마다 결과가 달라질 수 있다는 설명이 이어집니다. 중복이 있는 원문을 어떤 파서가 받아들였다는 사실만으로 다른 시스템에서도 같은 의미라고 보장할 수 없습니다. RFC 8259 §4

    다음 원문에서 이름 mode는 같은 객체 안에 두 번 나옵니다.

    {
      "mode": "preview",
      "mode": "normal"
    }
    

    Python의 기본 json.loads()는 반복된 이름을 받아들이고 마지막 값을 남깁니다. 따라서 이 원문을 읽은 딕셔너리에는 mode 하나와 normal만 남습니다. Python의 반복 이름 처리

    이후 딕셔너리를 다시 JSON으로 출력하면 겉보기에는 정상적인 한 줄이 됩니다. 하지만 그 과정은 원문이 중복 없이 작성됐음을 확인한 것이 아닙니다. 앞의 preview를 잃은 뒤 남은 결과를 정리했을 뿐입니다. 오류를 조사할 때 파싱 후의 로그만 보면 원문에서 무슨 일이 있었는지 놓칠 수 있습니다.

    값 검사보다 먼저 중복을 처리해야 합니다

    mode가 문자열이고 허용값이 preview 또는 normal인지 검사한다고 해보겠습니다. 파싱 후 객체에는 normal만 있으므로 이 규칙을 통과할 수 있습니다. 검사 함수가 아무리 꼼꼼해도 전달받지 못한 앞의 이름·값 쌍을 복원할 수는 없습니다.

    검사 순서를 다음처럼 잡으면 역할이 분명해집니다.

    1. JSON을 읽으면서 같은 객체 안의 이름 중복을 거부합니다.
    2. 만들어진 객체에 필수 필드와 자료형 규칙을 적용합니다.
    3. 값 조합이 서비스나 설정의 업무 규칙에 맞는지 판단합니다.

    첫 단계가 빠졌을 때 뒤의 schema 검사를 더 강하게 만든다고 중복 문제까지 해결되지는 않습니다. 프레임워크가 요청 본문을 자동으로 딕셔너리로 바꿔 준다면, 처리 함수에 들어오기 전에 최초 파싱이 이미 끝났을 수 있습니다. 중복 정책은 그 파싱 지점에 적용해야 합니다.

    이 글에서는 중복을 거부하는 정책을 선택합니다. 설정 병합에 우선순위가 필요하다면 병합 코드에서 어느 쪽이 이기는지 명시하고, 최종 JSON에는 결정한 값 하나를 쓰는 편이 낫습니다. 중복 이름을 우선순위 표현으로 사용하면 소비하는 파서에 의미가 의존합니다.

    Python에서는 쌍 목록을 받는 훅을 씁니다

    object_pairs_hook은 JSON 객체의 이름·값 쌍을 순서가 있는 목록으로 받습니다. 딕셔너리가 완성되기 전에 같은 이름이 다시 등장하는지 확인할 수 있는 지점입니다. 이미 딕셔너리를 받는 object_hook과 용도가 다릅니다. Python JSON 디코딩 훅

    import json
    
    def reject_duplicates(pairs):
        result = {}
        for name, value in pairs:
            if name in result:
                raise ValueError(
                    "duplicate name"
                )
            result[name] = value
        return result
    
    def load_config(raw):
        return json.loads(
            raw,
            object_pairs_hook=(
                reject_duplicates
            ),
        )
    
    raw = (
        '{"mode":"preview",'
        '"mode":"normal"}'
    )
    config = load_config(raw)
    

    이 예제는 두 번째 mode에서 ValueError를 내도록 작성했습니다. 앞의 값을 덮어쓰지 않고 설정 읽기를 실패시키는 동작입니다. 호출부는 이 오류를 사용자나 운영자에게 알려 수정할 기회를 줘야 합니다. 예외를 잡은 뒤 훅 없는 json.loads()로 다시 읽으면 거부 정책이 사라집니다.

    오류 메시지에는 원문 전체를 넣지 않았습니다. 실제 설정에는 비밀 값이 포함될 수 있기 때문입니다. 개발자가 어느 위치를 고쳐야 하는지 알려 주는 상세 진단이 필요하면, 경로를 추적할 수 있는 파서나 검증 도구를 선택할 수 있습니다. 이 짧은 훅은 객체의 위치를 표시하는 완성형 진단기는 아닙니다.

    서로 다른 객체의 mode는 중복이 아닙니다

    문서 전체에서 단어를 세는 검사는 범위가 너무 넓습니다. 아래 두 mode는 각각 left와 right 객체에 속하므로 이 정책에서 허용됩니다.

    {
      "left": {"mode": "preview"},
      "right": {"mode": "normal"}
    }
    

    훅은 객체마다 별도로 호출되고 매번 새 result를 만듭니다. 그래서 다른 객체의 이름을 서로 충돌시키지 않습니다. 반대로 left 내부에 mode가 두 번 있으면 최상위 객체가 멀쩡해도 거부합니다. 최상위 키만 검사하는 구현과 차이가 나는 지점입니다.

    이스케이프 표현도 봐야 합니다. 다음 Python 원문 문자열은 역슬래시가 JSON 파서에 전달되도록 raw string을 사용했습니다.

    raw = (
        r'{"mode":"preview",'
        r'"\u006dode":"normal"}'
    )
    load_config(raw)
    

    JSON에서 \u006d는 문자 m입니다. 따라서 파서가 이름을 해석한 뒤에는 두 이름 모두 mode가 되고, 같은 훅에서 거부할 수 있습니다. RFC 역시 이스케이프를 해석하지 않은 문자열 비교가 같은 문자열을 다르게 볼 수 있다고 설명합니다. RFC 8259 §8.3

    정규식으로 따옴표 안의 단어만 모으면 이런 해석과 객체 범위를 함께 처리하기 어렵습니다. 값에 들어간 문자열까지 이름으로 오인할 수도 있습니다. 파서가 이미 제공하는 쌍 목록을 쓰는 이유는 단순히 코드가 짧아서가 아닙니다.

    실제 입력 경로에서도 같은 정책인지 확인합니다

    함수 하나의 검수에는 정상 객체, 최상위 중복, 중첩 중복, 서로 다른 객체의 같은 이름, 이스케이프된 같은 이름을 넣습니다. 정상 입력은 남기고 중복 입력만 거부하는지 보려는 구분입니다. 거부 후 기본 파서로 재시도하는 경로가 없는지도 확인합니다.

    서비스 앞단에 별도 변환기가 있다면 같은 원문이 어디에서 처음 객체가 되는지 따라가야 합니다. 앞단이 이미 한 값만 남겼다면 뒤에 설치한 중복 훅에는 볼 자료가 없습니다. 입력을 처음 받는 구성 요소와 실제 소비자가 같은 정책을 따르는지 확인하는 것이 통합 단계의 과업입니다.

    이 훅은 중복 이름만 다룹니다. 허용 필드, 숫자 범위, 입력 크기와 중첩 깊이는 별도 기준이 필요합니다. 중복을 거부하는 데 성공한 뒤에는 앞에서 정한 schema와 업무 규칙 검사로 이어가세요. 원문을 객체로 바꾸는 순간 잃는 정보가 무엇인지 알면, 각 검사를 어느 위치에 둘지도 정할 수 있습니다.

    728x90
    반응형
Designed by Tistory.