JSON 포매팅·검증·JSON Schema의 차이와 실무 활용

2026-04-13 공개 8분 읽기

요약 (TL;DR)

지난달 한 팀이 feature_flags.json 한 줄을 고쳐 배포했습니다. JSON.parse는 통과했고, CI도 초록색이었고, staging에 올렸더니 flags.checkout_v2"true"라는 문자열이라 모든 결제 분기가 구버전으로 떨어졌습니다. “JSON을 확인했다”는 말이 실제로는 세 가지 작업 — 포매팅·문법 검증·구조 검증 — 의 합성어인데, 그날 팀은 앞 두 단계만 수행한 셈이었습니다. 포매팅(pretty-print) 은 공백과 줄바꿈을 다시 넣어 모양만 바꿀 뿐 아무것도 검증하지 않습니다. 문법 검증(syntax validation) 은 RFC 8259 기준으로 괄호·따옴표·이스케이프가 옳은지 보는 단계로, JSON.parse가 바로 이 일을 합니다. 구조 검증(structural validation) 은 그 이후, “이 모양이 내 코드가 기대하는 모양인가”를 묻는 단계이며 JSON Schema가 만들어진 이유가 정확히 이것입니다. 세 단계를 “JSON 검증 버튼 하나”로 묶어 생각하는 순간 위 사례 같은 무음 장애가 나옵니다. 읽을 때는 포매팅, 입력을 받을 때는 파싱, API 경계·설정 파일·메시지 경계 같은 신뢰 경계에서는 Ajv 같은 검증기로 스키마까지 — 세 단계를 각자 자리에 두는 파이프라인이 안전합니다.

배경/개념

JSON은 RFC 8259(및 동등한 ECMA-404)로 정의됩니다. 문법은 작고 단순합니다. 문서는 문자열, 숫자, true, false, null, 배열, 객체 중 하나이며, 문자열은 큰따옴표로 감싸고 \n, \t, \", \\, \uXXXX(유니코드 코드 유닛) 같은 이스케이프를 지원합니다. 숫자는 정수와 실수를 구분하지 않고 10진 문법을 따릅니다. 객체는 문자열 키를 가진 멤버들의 순서 없는 모음이고, 배열은 순서 있는 값의 목록입니다. 문자열 바깥의 공백은 의미가 없습니다.

JSON이 의도적으로 포함하지 않는 것도 중요합니다. 주석이 없고, 마지막 쉼표(trailing comma)가 없고, 작은따옴표 문자열·16진수·2진수 리터럴도 허용되지 않습니다. 비-ASCII 문자는 UTF-8 바이트로 그대로 쓰거나 \u 이스케이프로만 표현합니다. JSON5나 HJSON은 일부 규칙을 완화한 별개의 포맷이라, 엄격한 JSON 파서는 이를 받지 않습니다.

파싱에 성공했다는 건 “문법적으로 올바르다”만을 의미할 뿐, “의미적으로 옳다”와는 다릅니다. {"user": "x", "pass": "y"}는 완벽히 유효한 JSON이지만, 엔드포인트가 {"username": "...", "password": "..."}를 기대했다면 애플리케이션 관점에서는 잘못된 데이터입니다. 이 문제를 잡으려면 스키마 — 기계가 읽을 수 있는 “허용 가능한 문서의 형태 명세” — 가 필요하고, 그 표준이 JSON Schema입니다(현재 권장 메타스키마는 Draft 2020-12). 필수 필드, 타입, enum·const, 정규식으로 문자열 패턴, 숫자 범위, 배열 items·유일성, 객체 properties·additionalProperties, allOf·oneOf·anyOf·$ref로 조합까지 지원합니다. Ajv 8.12 같은 검증기는 스키마를 한 번 컴파일해 함수로 만들어 두므로, 핫패스에서도 검사 비용이 거의 무시할 수준입니다 — 제가 운영하던 한 API에서는 컴파일된 Ajv 검증 한 번이 평균 수십 마이크로초 영역이었습니다.

포매팅은 이 중 가장 단순합니다. 의미를 바꾸지 않고 공백과 들여쓰기만 조정합니다. JSON.stringify(obj, null, 2), jq ., IDE 포매터가 이미 하는 일이고, 사람이 읽기 위한 용도일 뿐 기계에는 의미가 없습니다.

비교/데이터

관점포매팅문법 검증JSON Schema 검증
목적읽기 쉽게 공백 정렬문자열이 JSON으로 파싱되는지 확인파싱된 값이 기대한 모양인지 확인
감지없음(공백만 조정)괄호 불일치, 따옴표 오류, 잘못된 이스케이프, 후행 쉼표필수 필드 누락, 타입 오류, 범위 초과, 알 수 없는 키
감지 못함구조·의미 문제기대한 모양·비즈니스 규칙스키마에 없는 규칙, 필드 간 관계
일반 도구JSON.stringify(obj, null, 2), jq, IDE 포매터JSON.parse, jq -e, 각 언어 파서Ajv 8.x, python-jsonschema, OpenAPI 검증기
적용 위치개발 도구·로그모든 파싱 경계(암묵적)API 요청/응답, 설정 로드, 메시지 경계

세 열은 “택 1”의 관계가 아니라 순차 계층입니다. 12MB짜리 package-lock.json을 한 줄로 둔 채 들여다보다가 들여쓰기가 들어간 형태로 보기 좋게 바꾸는 일이 포매팅이고, 그게 “검증”으로 이름이 바뀌는 순간 사고가 시작됩니다. 읽기 위해 포매팅하고, 문자열이 망가지지 않았는지 파싱으로 걸러내고, 모양이 맞는지 스키마로 확인합니다. 앞 두 단계만 적용하고 마지막을 빠뜨리는 것이 흔한 구멍입니다.

실전 시나리오

시나리오 1 — API 응답 디버깅. 외부 결제 게이트웨이가 600줄짜리 JSON을 한 줄로 돌려줍니다. 브라우저 개발자 도구의 Network 패널·로컬 포매터·curl ... | jq . 같은 파이프 한 번이면 사람이 훑을 수 있는 모양으로 바뀝니다. 여기서는 검증이 일어나지 않고, 목적은 “어디가 이상한지 눈으로 확인”뿐이며, 이 단계에서 “검증했다”고 말하지 않는 게 중요합니다.

시나리오 2 — 설정 파일 로딩. 서비스가 기동 시 config.json을 읽는다고 합시다. 엄격한 JSON 파서는 후행 쉼표 같은 문법 오류를 잡고 기동을 막아주므로 올바른 동작입니다. 하지만 도입부에서 본 사례처럼 retries: 3이어야 할 자리에 retries: "three"가 들어 있어도 파싱은 정상이고, 문제는 코드가 문자열을 숫자와 비교하려는 시점에야 드러납니다. 제가 한 팀에 권한 패턴은 이렇습니다 — 기동 첫 줄에서 Ajv compile(schema)로 만든 검증 함수를 호출해 필수 필드·타입·범위·enum을 확인하고, 위반 시 process.exit(1). 5분짜리 변경이 그 뒤로 이틀치 staging 사고를 두 번 막아줬습니다.

시나리오 3 — OpenAPI 계약 검증. 팀이 OpenAPI 3.1 문서에 엔드포인트·요청 바디·응답 형태를 components.schemas로 기술한다고 합시다. 계약 테스트는 명세의 예시 페이로드를 같은 스키마로 검증해, 서버 구현이 “문자열이어야 할 자리에 정수”를 반환하는 드리프트를 클라이언트가 깨지기 전에 잡아냅니다. 엔진은 외로운 설정 파일 검증과 동일한 JSON Schema 검증기이고, 차이는 적용 규모일 뿐입니다.

자주 하는 오해

JSON.parse만으로 검증은 끝이다.” 문법만 보는 것이지 모양을 보지 않습니다. 파서는 절반이 비어 있는 객체도 기꺼이 돌려줍니다. JSON.parse는 “망가진 문자열 차단” 역할로 두고, 신뢰 경계에서는 스키마 검사를 한 겹 더 얹으세요.

“JSON Schema는 서버 전용이다.” 브라우저에서 요청 전에 검증하면 사용자 피드백이 즉시 돌아오고 서버 부하도 줄어듭니다. Ajv를 비롯한 많은 검증기가 브라우저에서 잘 돕니다. 서버 검증은 여전히 필수지만 — 클라이언트를 신뢰하면 안 됩니다 — 클라이언트 단 검증은 UX를 개선하고 보안 모델을 약화시키지 않습니다.

“JSON5는 주석이 되는 JSON이다.” JSON5는 주석·무따옴표 키·후행 쉼표·16진수 리터럴 등을 더합니다. 사람이 편집하는 설정 포맷으로는 친절하지만(가장 유명한 사용처인 tsconfig.json은 사실 JSONC라는 또 다른 비표준 상위집합입니다) RFC 8259를 엄격히 따르는 소비자는 이를 받지 않습니다. 소비자가 명시적으로 지원하는 경우에만 JSON5/JSONC를 쓰고, 네트워크로 나가거나 임의의 파서를 거쳐야 한다면 반드시 엄격 JSON을 내보내세요.

“YAML은 들여쓰기 쓰는 JSON이다.” 모든 유효 JSON 문서는 유효 YAML이지만, YAML은 앵커·앨리어스, 태그 타입, 한 파일의 다중 문서, 블록·폴드 스칼라, 들여쓰기 민감 파싱 등 JSON에 없는 기능을 얹습니다. 유명한 함정은 “노르웨이 문제”: YAML 1.1이 NO를 따옴표 없이 쓰면 불리언 false로 해석합니다. “가독성 때문에” JSON을 YAML로 바꾸는 결정은 새로운 문제를 초대합니다.

체크리스트 또는 의사결정 플로우

  1. 그냥 읽고 싶을 뿐인가? 포매팅만 하세요. “검증했다”라고 부르지 마세요.
  2. 데이터가 신뢰 경계를 넘는가(HTTP 요청, 메시지 큐, 설정 파일)? 파싱뿐 아니라 JSON Schema까지.
  3. 알 수 없는 필드를 허용할지 결정했나? 스키마에 additionalProperties: false를 설정하고 거부·제거 정책을 정해 두세요.
  4. 에러 메시지가 행동 가능한가? Ajv의 allErrors: true처럼 모든 위반을 한 번에 보여주도록 검증기를 설정합니다.
  5. 스키마가 실제 코드 근처에 사는가? 명세와 구현의 드리프트는 스키마가 양쪽의 단일 진실원일 때 덜 벌어집니다.
  6. 포맷이 JSON에 맞는 수준인가? 사람이 편집하는 설정이고 파서를 직접 고를 수 있다면 JSON5/JSONC·TOML이 더 너그럽습니다. 기계 간 교환이라면 엄격 JSON을 유지하세요.

관련 도구

Patrache Studio의 JSON 포매터는 브라우저에서 실행되므로 붙여넣은 페이로드가 기기 밖으로 나가지 않습니다 — 개인정보를 포함하는 운영 응답을 들여다볼 때 유용합니다. JSON 페이로드는 혼자 다니지 않는데, 안에 인코딩된 바이너리가 들어 있다면 Base64·URL 인코딩: 왜 필요하고 언제 잘못 쓰는가에서 크기가 왜 늘어나는지, 언제 다른 전송 수단이 적절한지 다룹니다. ID가 포함돼 있다면 UUID v1·v4·v7 비교와 DB 기본키 설계에서 서버가 어떤 버전을 발급하느냐가 하위 시스템의 인덱싱·정렬·캐시에 왜 영향을 주는지 살펴볼 수 있습니다.

참고 자료