숫자와 null에서 대부분의 사고가 납니다
JSON 명세에는 정수와 실수의 구분이 없습니다. 그래서 언어마다 숫자를 어떻게 받을지 스스로 정하는데, 여기서 데이터가 조용히 망가집니다. JavaScript의 기본 숫자 타입은 배정밀도 부동소수점이라 안전하게 표현할 수 있는 정수 범위에 한계가 있습니다. 백엔드에서 64비트 정수 ID를 그대로 내보내면 프런트엔드에서 끝자리가 달라진 값을 받게 됩니다. 서버 로그에는 아무 오류도 남지 않습니다. 큰 정수 ID는 문자열로 보내는 것이 이 문제를 피하는 가장 확실한 방법입니다. Python은 임의 정밀도 정수를 갖지만 실수는 여전히 부동소수점이므로 금액 계산에는 별도 십진 타입이 필요합니다.
null도 만만치 않습니다. "필드가 없음", "필드가 있고 값이 null", "필드가 있고 기본값"은 서로 다른 세 상태인데, 많은 역직렬화 도구가 이를 하나로 뭉갭니다. Go의 구조체는 JSON에 없던 필드를 제로값으로 채우므로 0이 온 것인지 아예 안 온 것인지 구분되지 않습니다. 포인터 타입이나 별도 플래그를 써야 구분됩니다. 부분 수정 API에서 "이 필드를 건드리지 말라"와 "이 필드를 비우라"를 구분해야 할 때 반드시 걸리는 문제입니다. Rust의 Option과 Kotlin의 널 허용 타입은 이 구분을 타입에 담을 수 있는 쪽입니다.
이름 매핑과 알 수 없는 필드
- Go의 대소문자 규칙 — 패키지 외부에 공개되지 않은 소문자 필드는 직렬화 대상에서 아예 빠집니다. 구조체 태그 없이 쓰면 필드 이름이 대문자로 시작하는 형태로 나가므로, snake_case를 쓰는 API와 맞추려면 태그를 반드시 붙여야 합니다.
- PHP의 두 얼굴 —
json_decode는 기본적으로 객체를 돌려주고, 두 번째 인자를 참으로 주면 연관 배열을 돌려줍니다. 두 형태는 접근 문법이 달라서, 이 인자를 확인하지 않고 쓴 코드가 다른 함수로 옮겨지면 바로 깨집니다.
- 모르는 필드 정책 — 서버가 새 필드를 추가했을 때 클라이언트가 예외를 던질 것인지 무시할 것인지는 라이브러리 설정입니다. 무시가 기본인 쪽이 호환성에 유리하지만, 오타 난 필드도 함께 무시되어 값이 조용히 비는 대가가 따릅니다. 어느 쪽이든 의식적으로 고르는 편이 낫습니다.
- 날짜 — JSON에는 날짜 타입이 없습니다. ISO 8601 문자열, 초 단위 정수, 밀리초 단위 정수가 모두 쓰이고 표준은 없습니다. 시간대 정보가 빠진 문자열을 주고받으면 서버와 클라이언트가 서로 다른 시각으로 해석합니다. API 문서에 형식을 못 박아 두어야 합니다.
신뢰할 수 없는 입력을 파싱할 때는 크기와 깊이를 제한해야 합니다. 깊게 중첩된 JSON은 재귀 파서의 스택을 소진시킬 수 있고, 거대한 본문은 메모리를 먹습니다. 요청 본문 크기 제한을 서버 앞단에 두고, 스트리밍 파서를 지원하는 라이브러리라면 전체를 메모리에 올리지 않는 방식을 검토하는 것이 좋습니다. 또한 역직렬화가 임의의 타입을 생성하도록 허용하는 설정은 원격 코드 실행으로 이어진 전례가 있으므로, 다형 역직렬화를 켤 때는 허용 타입을 명시적으로 좁혀야 합니다.
정리하면 JSON 코드의 품질은 파싱 한 줄이 아니라 경계에서 결정됩니다. 외부에서 들어온 데이터는 내부 타입으로 한 번 변환하면서 검증하고, 그 뒤로는 검증된 타입만 흘려보내는 구조가 어느 언어에서든 가장 오래갑니다. 언어별 직렬화 상세는 Go encoding/json, Java Jackson, kotlinx.serialization, Python 파일과 데이터 문서에서 이어집니다.
JSON이 맞지 않는 경우
JSON은 사람이 읽을 수 있고 어디서나 파싱된다는 점 때문에 기본 선택이 되었지만, 모든 자리에 맞지는 않습니다. 대량의 수치 데이터를 주고받을 때는 텍스트 표현이 크기와 파싱 비용을 모두 키웁니다. 스키마를 강제해야 하는 서비스 간 통신에서는 별도의 정의 언어를 쓰는 이진 포맷이 호환성 관리에 유리합니다. 반대로 사람이 손으로 편집하는 설정 파일이라면 주석을 쓸 수 없다는 점이 불편해서, 주석과 후행 쉼표를 허용하는 다른 형식이 선호되기도 합니다.
선택 기준은 단순합니다. 읽는 주체가 사람인가 기계인가, 스키마가 자주 바뀌는가, 그리고 데이터 크기가 문제가 되는 규모인가입니다. 세 질문의 답이 모두 JSON 쪽이 아니라면 다른 형식을 검토할 시점입니다.