JSON, 언어 간 미묘한 차이: 단순함 뒤에 숨겨진 상호 운용성 문제

JSON is not JSON Across Languages | <span class="text-terminal-purple">Dochia</span> CLI Blog

작성자
Ruby Weekly
발행일
2025년 09월 26일

핵심 요약

  • 1 JSON은 명세의 단순함에도 불구하고, 각 프로그래밍 언어와 라이브러리마다 데이터 해석 방식에 미묘한 차이가 존재하여 상호 운용성 문제를 야기합니다.
  • 2 정수 정밀도 손실, 유니코드 정규화, 객체 키 순서 불일치, 날짜/시간 처리, null 값의 의미 등 다양한 영역에서 예상치 못한 문제가 발생할 수 있습니다.
  • 3 스키마 유효성 검사, 데이터 타입 표준화, 신중한 라이브러리 선택, 그리고 교차 언어 호환성 테스트를 통해 이러한 JSON 불일치 문제를 효과적으로 완화할 수 있습니다.

도입

JSON(JavaScript Object Notation)은 웹 API 및 시스템 통합의 사실상 표준이 된 간단하고 가벼우며 사람이 읽을 수 있는 데이터 교환 형식입니다. XML의 대안으로 자리매김하며 그 단순함이 강점으로 여겨졌습니다. 하지만 JSON 명세 자체는 명확하더라도, 실제 프로그래밍 언어와 라이브러리들은 특정 측면을 다르게 해석할 수 있습니다. 겉으로는 균일해 보이는 형식이 실제로는 미묘한 불일치, 엣지 케이스, 구현 세부 사항으로 이어져 개발자들이 다양한 플랫폼에서 작업할 때 인지해야 할 문제들을 발생시킵니다. 본 글은 이러한 JSON의 숨겨진 복잡성과 그로 인한 상호 운용성 문제들을 심층적으로 다룹니다.

JSON 명세는 객체, 배열, 문자열, 숫자, 불리언, null의 여섯 가지 데이터 타입을 정의하여 단순함을 강조합니다. 그러나 이러한 단순함은 해석의 여지를 남기며, 각 언어와 라이브러리가 이 여지를 다르게 채우면서 ‘보편적인’ 데이터 형식이 덜 보편적이게 됩니다.

1. 숫자 처리의 불일치

  • 정수 정밀도 손실: JavaScript는 64비트 부동소수점 표현으로 인해 특정 크기 이상의 정수에서 정밀도를 잃습니다. 반면 Python, Java(BigInteger), Go(int64) 등은 큰 정수를 정확히 처리할 수 있어, 데이터베이스 ID나 금융 데이터와 같은 중요한 값에서 문제가 발생합니다.

  • 부동소수점 정밀도: JSON 자체의 문제는 아니지만, IEEE 754 부동소수점 표준으로 인해 십진수 연산에서 미묘한 오차가 발생할 수 있습니다. 금융 애플리케이션에서는 Python의 Decimal이나 Java의 BigDecimal과 같은 전용 타입을 사용해야 합니다.

2. 문자열 인코딩 및 정규화

  • 유니코드 정규화: 유니코드 문자는 단일 코드포인트 또는 결합 문자로 표현될 수 있어, 시각적으로 동일하더라도 바이트 시퀀스가 달라질 수 있습니다. 이는 문자열 비교 시 불일치를 야기하며, NFC와 같은 표준 정규화가 필수적입니다.

3. 객체 키 순서 및 Null 처리

  • 객체 키 순서: JSON 명세는 키 순서의 중요성을 부정하지만, JavaScript, Python, Ruby 등 일부 언어는 삽입 순서를 유지합니다. 반면 Go는 이터레이션 순서가 무작위이며 직렬화 시 키를 정렬합니다. 이러한 불일치는 암호화 해시(HMAC) 생성 등 바이트 일관성이 요구되는 상황에서 치명적인 문제를 일으킵니다.

  • Null vs. Undefined vs. Missing: 각 언어는 값의 부재를 다르게 해석합니다. JavaScript의 undefinedJSON.stringify에서 생략되지만, 다른 언어에서는 null로 변환되거나 키 부재와 혼동될 수 있습니다.

4. 날짜 및 시간 형식

  • JSON은 날짜 타입을 명시하지 않아 ISO 8601 문자열, Unix 타임스탬프(초/밀리초) 등 다양한 형식이 사용됩니다. JavaScript의 Date 객체는 밀리초를 기준으로 하므로, Unix 초 단위를 그대로 사용하면 잘못된 날짜로 해석될 수 있습니다.

5. 오류 처리 불일치

  • 중복 키, 후행 쉼표, 선행 0, 작은따옴표 사용 등 JSON 명세에 어긋나는 형식에 대해 각 파서가 오류를 발생시키거나 특정 규칙에 따라 처리하는 방식이 다릅니다. 이는 예상치 못한 동작이나 파싱 실패로 이어질 수 있습니다.

6. 실제 사례 및 완화 전략

  • 실제 사례: Twitter ID는 JavaScript의 안전 정수 범위를 초과하여 id_str 필드를 별도로 제공합니다. PostgreSQL의 JSON 타입은 원본 형식을 유지하지만 JSONB는 키를 정렬하여 정규화합니다. MongoDB의 확장 JSON($date, $oid)은 표준 JSON 파서로는 처리할 수 없습니다.

  • 완화 전략:

    • 스키마 유효성 검사: JSON Schema를 통한 엄격한 데이터 형식 정의 및 검증.
    • 데이터 타입 표준화: 대규모 정수를 문자열로, 날짜를 ISO 8601로 변환하는 등 명확한 컨벤션 수립.
    • 라이브러리 신중 선택: 정밀도와 안전성을 보장하는 JSON 라이브러리 사용 및 적절한 설정.
    • 교차 언어 호환성 테스트: 다양한 언어로 구현된 서비스 간 데이터 왕복 테스트를 CI/CD 파이프라인에 통합하여 잠재적 불일치 조기 발견.

결론

JSON의 우아한 단순성은 양날의 검입니다. 명세는 직관적이지만, 다른 팀, 다른 언어, 다른 가정으로 구축된 시스템 간에 데이터를 이동할 때 복잡성이 드러납니다. 대규모 정수의 정밀도 손실, 유니코드 문자열의 손상, 예상치 못한 객체 키 순서 변경, 날짜 파싱의 불일치 등은 개발자에게 큰 골칫거리가 될 수 있습니다. 특히 이러한 문제들은 엣지 케이스에 숨어 있다가 프로덕션 환경에서 예측 불가능하게 나타나 디버깅을 매우 어렵게 만듭니다. 따라서, JSON이 모든 언어에서 동일하게 작동한다는 환상을 버리고, 명확한 컨벤션 수립, 엄격한 스키마 유효성 검사, 신중한 라이브러리 선택, 그리고 철저한 교차 언어 호환성 테스트를 통해 이러한 불일치에 대비해야 합니다. JSON의 본질적인 한계를 인정하고 적극적인 엔지니어링 규율을 적용해야만 안정적인 시스템을 구축할 수 있습니다.

댓글 0

댓글 작성

0/1000
정중하고 건설적인 댓글을 작성해 주세요.

아직 댓글이 없습니다

첫 번째 댓글을 작성해보세요!