API JSON 검증: 구조·필드 존재·정확한 수량을 확인하기

English version

TECH INSIGHT / EXPLAINED

JSON 해독 성공은 문법이 맞다는 뜻이지 유효한 시세나 주문 결과라는 뜻은 아닙니다. HTTP 상태·응답 모양·제공자 성공 코드·실제 사용할 값까지 나누어 검증하는 경계를 만듭니다.

세 사람이 JSON 구조와 숫자를 검증한 뒤 애플리케이션으로 전달합니다.
AI로 제작한 설명용 일러스트. 세 사람이 JSON 구조와 숫자를 검증한 뒤 애플리케이션으로 전달합니다.

실제 프로젝트의 로컬 실행 화면

기존 프로젝트의 화면 또는 함수를 로컬에서 실행해 촬영했습니다. 개인정보 없는 샘플 입력을 사용했으며, 실행한 범위는 각 이미지 아래에 설명했습니다.

원본 KISClient._parse에 누락 필드·배열·null·숫자 성공 코드를 넣은 실행 리포트. rt_cd 누락이 통과하고 배열·null에서 AttributeError가 발생하는 한계가 확인됐다. 글의 방어적 검증 예제와 구분되는 기존 프로젝트 코드의 관측 결과다.
원본 KISClient._parse에 누락 필드·배열·null·숫자 성공 코드를 넣은 실행 리포트. rt_cd 누락이 통과하고 배열·null에서 AttributeError가 발생하는 한계가 확인됐다. 글의 방어적 검증 예제와 구분되는 기존 프로젝트 코드의 관측 결과다.

값을 꺼내기 전에 응답 구조 검증

Requests에서는 HTTP 상태를 확인하고 response.json() 주위에서 requests.exceptions.JSONDecodeError를 처리합니다. 성공 응답이 비어 있어도 되는지는 엔드포인트마다 다릅니다. JSON 배열·null·HTML 오류 페이지도 각각 규약에 맞춰 판단해야 합니다.

(output or {})는 일부 형태의 null만 처리합니다. 비어 있지 않은 배열·문자열에서는 여전히 실패하고 잘못된 false를 숨길 수도 있습니다. .get() 전에 딕셔너리인지 확인하세요. 필수 성공 코드가 rt_cd인 엔드포인트에서 누락을 성공으로 간주하면 안 됩니다.

분수 수량을 버리지 말고 거절하기

int(float(value))는 "12.9"를 12로 자르고 큰 정수를 손상시킬 수 있습니다. 아래 오프라인 예제는 "12.0" 같은 정수값 소수 문자열은 허용하되 bool·분수·NaN·무한대는 거절합니다. 검증 예외를 발생시켜 호출부에서 복구 규칙을 명시적으로 선택하게 합니다.

import json
from decimal import Decimal, InvalidOperation

def reject_constant(value):
    raise ValueError("non-standard JSON numeric constant")

def whole_quantity(value):
    if type(value) not in (str, int, Decimal):
        raise ValueError("quantity has the wrong type")
    try:
        number = Decimal(value)
    except InvalidOperation as exc:
        raise ValueError("invalid quantity") from exc
    if not number.is_finite() or number < 0 or number != number.to_integral_value():
        raise ValueError("quantity must be a finite nonnegative integer")
    return int(number)

def parse_example(raw):
    # Example contract, not a universal exchange response schema.
    if len(raw) > 1_048_576:
        raise ValueError("response exceeds this application's size limit")
    body = json.loads(raw, parse_float=Decimal, parse_constant=reject_constant)
    if not isinstance(body, dict) or body.get("rt_cd") != "0":
        raise ValueError("missing success envelope or business failure")
    output = body.get("output")
    if not isinstance(output, dict) or "qty" not in output:
        raise ValueError("missing output quantity")
    return whole_quantity(output["qty"])

assert parse_example('{"rt_cd":"0","output":{"qty":"12.0"}}') == 12
assert whole_quantity("9007199254740993") == 9007199254740993

운영 대응은 값의 역할에 맞추기

선택적 대시보드 필드는 비어 있어도 될 수 있습니다. 반면 가격·주문 수량·상태 대조 자료가 틀리면 구조화된 오류를 남기고 그 값에 근거한 판단을 막아야 합니다. 잘못된 숫자를 모두 0으로 바꾸거나 알림 없이 계속 건너뛰지 않습니다.

엔드포인트 이름·상태·안전한 오류 분류·추적 ID를 기록합니다. 본문 앞부분에도 토큰·계좌 자료·반사된 요청이 있을 수 있으므로 잘라내기는 비식별화가 아닙니다. 재시도 여부는 작업과 오류 의미에 달렸습니다. 주문 결과 불명은 대조가 필요하고 일부 업무 오류는 일시적일 수도 있습니다.

Go도 값 검증이 필요

encoding/json은 유효한 JSON이라도 대상 타입과 맞지 않으면 거절할 수 있습니다. 새로 초기화한 struct의 누락 필드는 제로값이지만 재사용한 struct는 이전 값을 유지할 수 있습니다. 포인터는 숫자 0과 nil을 구분하지만 새 포인터 필드에서 누락과 명시적 null은 둘 다 nil입니다. 둘의 구분이 필요하면 별도 존재 여부 모델을 사용하세요.

가격 비교 전에 math.IsNaN/math.IsInf로 유한하지 않은 값을 거절합니다. 구분자 피드는 인덱싱 전에 메시지 버전·필드 수·문서의 타입을 검증하세요. 고정 필드 수 하나나 여섯 글자 종목 규칙은 보편적이지 않습니다. 복잡한 계약을 반복 검증할 때 스키마 라이브러리가 유용하지만 엔드포인트 개수만으로 필요 여부가 결정되지는 않습니다.

공식 자료와 함께 읽기

Requests JSON errors · Python JSON decoder · Go encoding/json

내용·예제 검토: 2026-09-29

댓글