Comet5 블로그
개발·운영

Python JSON 설정 검증: 누락·null·bool·정수 구분하기

Comet5 · 최초 발행 · 약 5분

JSON 파싱 성공 뒤에도 필요한 타입 검증을 설명하고 true가 정수 검사에 통과하는 문제를 재현합니다.

이 글의 목차

설정 파일을 json.loads()로 읽었다고 실행 가능한 설정이 된 것은 아닙니다. JSON 문법이 맞는지와 우리 프로그램이 기대한 값인지는 다른 질문입니다. 이 글에서는 재시도 횟수와 활성화 여부만 있는 작은 설정을 검증합니다.

헷갈리기 쉬운 네 가지 입력

예제에서 retries는 0부터 5까지의 정수이고 생략하면 3입니다. enabled는 true 또는 false이며 생략하면 false입니다. 명시적인 null은 기본값으로 바꾸지 않고 잘못된 입력으로 거절합니다. 오타를 빨리 찾기 위해 알 수 없는 키도 거절합니다.

Python에서 bool은 int의 하위 타입이라 isinstance(True, int)는 참입니다. JSON의 true가 재시도 횟수 1처럼 통과하는 일을 막으려면 이 계약에서는 정확한 타입을 확인해야 합니다. 모든 프로그램에서 하위 타입을 거절해야 한다는 일반 규칙은 아닙니다.

파싱과 검증을 분리한 예제

import json

def load_settings(text):
    try:
        raw = json.loads(text)
    except json.JSONDecodeError:
        raise ValueError('JSON 문법 오류') from None
    if type(raw) is not dict:
        raise ValueError('최상위 값은 객체여야 합니다')
    unknown = set(raw) - {'retries', 'enabled'}
    if unknown:
        raise ValueError('알 수 없는 키: ' + ', '.join(sorted(unknown)))
    retries = raw.get('retries', 3)
    enabled = raw.get('enabled', False)
    if type(retries) is not int or not 0 <= retries <= 5:
        raise ValueError('retries는 0~5 정수여야 합니다')
    if type(enabled) is not bool:
        raise ValueError('enabled는 boolean이어야 합니다')
    return {'retries': retries, 'enabled': enabled}

assert load_settings('{}') == {'retries': 3, 'enabled': False}
assert load_settings('{"retries": 0, "enabled": true}') == {'retries': 0, 'enabled': True}
for text in ('{"retries": true}', '{"retries": null}', '{"retries": 1.0}',
             '{"enabled": "false"}', '{"retry": 2}', '[]', '{'):
    try:
        load_settings(text)
    except ValueError as error:
        print(text, '->', error)
    else:
        raise AssertionError(text)

정상 입력 두 개는 검사만 통과하고 실패 입력 일곱 개는 이유를 출력합니다. 1.0은 수학적으로 정수와 같아도 JSON 디코딩 결과가 float라서 이 계약에서는 거절합니다. 숫자 문자열을 자동 변환하고 싶다면 허용 형식을 따로 정하고 테스트해야 합니다.

기본값이 오류를 숨기지 않게 하기

raw.get('retries') or 3이라고 쓰면 올바른 0도 3으로 바뀝니다. 위 코드는 키가 없을 때만 기본값을 쓰고 null은 None으로 남겨 검증에서 거절합니다. 누락과 명시적 해제를 구분하는 설정이라면 이 차이가 특히 중요합니다.

입력 결과 이유
키 생략 기본값 3 명시한 기본 정책
0 허용 재시도하지 않음
null 거절 정수 계약 위반
true 거절 boolean과 횟수 구분
"3" 거절 문자열 자동 변환 안 함

환경변수와 파일을 함께 쓴다면 우선순위도 기록해야 합니다. 환경변수 값은 문자열이므로 JSON에서 쓴 타입 검사를 그대로 적용하기 전에 별도의 변환 규칙이 필요합니다. 특히 bool('false')는 True여서 문자열 boolean 해석에 사용하면 안 됩니다.

이 검증기가 다루지 않는 범위

기본 JSON 디코더는 중복 키가 있으면 마지막 값을 유지합니다. 설정 작성 실수를 엄격히 막으려면 object_pairs_hook으로 중복 키도 검사할 수 있습니다. 이 예제는 그 기능과 파일 크기 제한을 구현하지 않습니다. 외부 사용자가 올리는 파일이라면 읽기 크기 제한과 오류 메시지의 민감정보 노출 여부도 별도로 확인합니다.

설정 검증은 앱 시작 때 한 번 수행하고 실패하면 원인을 명확히 보여 주는 편이 좋습니다. 잘못된 값을 조용히 기본값으로 바꿔 실행하면 장애가 났을 때 실제 적용값을 찾기 어려워집니다. 단, 비밀키나 토큰을 포함한 설정 전체를 진단 로그에 남기지는 않습니다.

참고 자료