~ / posts / developer
💻 개발자

JSON YAML XML 차이 — 용량·파싱 속도 실측과 고르는 기준

새 프로젝트 설정 파일 포맷을 정하는 회의에서 "그냥 JSON 쓰죠", "주석이 안 되잖아요", "그럼 YAML?", "들여쓰기 하나 틀려서 배포 날려본 적 있어요?" 같은 대화가 오가는 걸 몇 번 봤어요. 결론부터 말하면 기계끼리 주고받는 데이터는 JSON, 사람이 읽고 고치는 설정은 YAML(따옴표 습관 필수), 기존 표준이 요구하는 문서형 데이터는 XML이 기본값이에요.

다만 "왜 그런지"를 알면 예외 상황에서도 판단이 쉬워져요. 이번 글에서는 같은 사용자 데이터 1,000건을 세 포맷으로 직접 직렬화해서 용량과 파싱 시간을 재 보고, 실제로 사고가 나는 YAML 타입 추론 함정을 파이썬으로 재현해 봤어요.

핵심 요약

같은 데이터 기준으로 압축 JSON과 YAML은 용량이 거의 같고, 들여쓴 JSON은 1.73배, 요소형 XML은 1.49배 커요.

파싱 속도는 JSON이 압도적으로 빠르고, 파이썬 YAML은 같은 데이터에서 JSON보다 수백 배 느렸어요.

YAML 1.1 파서는 NO를 false로, 12:30을 750으로, 012345를 8진수로 읽어요. 문자열은 따옴표로 감싸는 게 안전해요.

XML은 SVG, 오피스 문서, RSS, 공공·금융 연동처럼 표준이 정해진 곳에서 여전히 현역이에요.

같은 데이터, 세 가지 모양

사용자 한 명을 표현한다고 해 볼게요. 세 포맷은 "무엇으로 구조를 표시하느냐"가 다릅니다.

같은 사용자 데이터를 JSON은 중괄호와 따옴표로, YAML은 들여쓰기로, XML은 여는 태그와 닫는 태그로 표현한 비교 카드
구조를 표시하는 방식의 차이

JSON은 중괄호·대괄호·큰따옴표가 구조를 정해요. 공백은 의미가 없어서 한 줄로 압축해도 같은 데이터예요. YAML은 들여쓰기가 곧 구조라서 공백 한 칸이 의미를 바꿉니다. XML은 여는 태그와 닫는 태그가 쌍을 이루고, 값을 속성(<user name="smith">)으로 둘지 자식 요소로 둘지도 설계자가 정해야 해요.

참고로 YAML 1.2는 JSON을 거의 그대로 포함하도록 설계돼서, 대부분의 JSON 문서는 YAML 파서로도 읽혀요. "YAML은 JSON의 상위 집합"이라는 말이 여기서 나왔어요.

한눈에 비교표

항목JSONYAMLXML
표준RFC 8259 / ECMA-404YAML 1.2 (2009), 많은 파서는 1.1W3C XML 1.0
주석불가가능 (#)가능 (<!-- -->)
데이터 타입문자열·숫자·불리언·null·배열·객체JSON 타입 + 날짜, 앵커/참조 등전부 문자열 (스키마로 타입 부여)
손으로 쓰기보통 (쉼표·따옴표 실수)편함 (들여쓰기 실수)번거로움
파싱 속도매우 빠름느림빠름
스키마 검증JSON SchemaJSON Schema 활용XSD, DTD, RELAX NG
보안 주의점거의 없음임의 객체 생성 로더XXE(외부 엔티티)
주 사용처REST API, 웹, 로그쿠버네티스, CI, 앱 설정SVG, docx·xlsx, RSS, SOAP

표에서 눈여겨볼 건 "데이터 타입" 줄이에요. JSON은 타입이 문법에 박혀 있어서 "1" 과 1 이 확실히 구분돼요. YAML은 따옴표가 없으면 파서가 타입을 추측하고, XML은 아예 모든 값이 문자열이라 해석은 받는 쪽 몫이에요. 실무 사고의 대부분이 이 차이에서 나옵니다.

용량 비교 — 같은 데이터 1,000건을 직접 직렬화

id, name, email, roles(배열), active, score 필드를 가진 사용자 1,000명을 파이썬으로 만들어 각 포맷으로 저장해 봤어요.

import json, yaml, xml.etree.ElementTree as ET
users = [{"id": i, "name": f"user{i}", "email": f"user{i}@example.com",
          "roles": ["admin", "dev"] if i % 3 == 0 else ["dev"],
          "active": i % 2 == 0, "score": round(i * 1.37, 2)} for i in range(1000)]
data = {"users": users}

jmin = json.dumps(data, separators=(',', ':'))
jpretty = json.dumps(data, indent=2)
y = yaml.safe_dump(data, sort_keys=False)
print(len(jmin.encode()), len(jpretty.encode()), len(y.encode()))
# 105942 182956 106606

XML은 모든 필드를 자식 요소로 둔 버전과, 단순 필드를 속성으로 둔 버전 두 가지로 만들었어요.

사용자 1000건 직렬화 크기 (압축 JSON = 1.0 기준 KB)
압축 JSON압축 JSON · 104KB104KBYAMLYAML · 104KB104KBXML 속성형XML 속성형 · 114KB114KBXML 요소형XML 요소형 · 155KB155KB들여쓴 JSON들여쓴 JSON · 179KB179KB

들여쓴 JSON(indent=2)이 가장 크다 — 공백이 73%를 더한다

단위: KB · 자료: Python 3.9 · PyYAML 6.0.3 · xml.etree 로 직접 직렬화해 UTF-8 바이트 수 측정 (1KB=1024바이트)
표로 보기
구분크기
압축 JSON104
YAML104
XML 속성형114
XML 요소형155
들여쓴 JSON179

결과가 흥미로워요. "XML은 무겁다"는 건 요소형(태그 이름이 여는 쪽·닫는 쪽에 두 번 나옴)일 때 얘기고, 속성형 XML은 압축 JSON보다 10% 정도만 커요. 반대로 들여쓴 JSON이 제일 컸어요. API 응답을 indent=2 로 내보내는 서버가 의외로 많은데, 이 데이터 기준으로 73%가 공백이에요. 물론 실제 전송에서는 gzip·brotli 압축이 반복 패턴을 대부분 지워 주니, 전송량 차이는 이보다 훨씬 작아요. 압축 전 크기는 로그 저장, 메모리, 메시지 큐 페이로드 한도에서 의미가 있어요.

파싱 속도 — YAML이 느린 이유

같은 데이터를 각 파서로 읽는 데 걸린 시간도 재 봤어요.

import timeit
from yaml import CSafeLoader
print(min(timeit.repeat(lambda: json.loads(jmin), number=1, repeat=30)))
print(min(timeit.repeat(lambda: yaml.load(y, Loader=CSafeLoader), number=1, repeat=5)))
사용자 1000건 파싱 시간 (밀리초 · 낮을수록 빠름)
json.loadsjson.loads · 1.3ms1.3msElementTreeElementTree · 3.9ms3.9msYAML C 로더YAML C 로더 · 82ms82msYAML 순수 파이썬YAML 순수 파이썬 · 708ms708ms

같은 YAML이라도 libyaml 기반 CSafeLoader 가 순수 파이썬 safe_load 보다 약 9배 빨랐다

단위: ms · 자료: Apple M1 · macOS · Python 3.9.6 · PyYAML 6.0.3 에서 직접 측정. 다른 작업이 함께 돌던 환경이라 절대값은 2~3배 흔들렸지만 순서는 매번 같았음
표로 보기
구분파싱 시간
json.loads1.3
ElementTree3.9
YAML C 로더82
YAML 순수 파이썬708

JSON 파서는 문법이 작아서 거의 메모리 복사 수준으로 빨라요. YAML은 들여쓰기 추적, 타입 추론, 앵커·별칭 처리까지 해야 해서 본질적으로 무거워요. 파이썬에서 YAML을 많이 읽는다면 yaml.load(text, Loader=yaml.CSafeLoader) 로 C 로더를 쓰는 것만으로 체감이 확 달라져요(libyaml이 설치된 경우).

이 숫자의 실무적 의미는 이래요. 설정 파일은 앱 시작 때 한 번 읽으니 YAML이 느려도 아무 상관없어요. 하지만 요청마다 주고받는 API 페이로드나 대량 로그를 YAML로 하면 CPU를 낭비하게 됩니다. "사람이 읽는 빈도"와 "기계가 읽는 빈도" 중 어느 쪽이 큰지로 고르면 거의 틀리지 않아요.

YAML 함정 — 노르웨이 문제와 암묵적 타입 변환

YAML의 진짜 무서운 점은 문법 에러가 아니라 "문법상 맞는데 의도와 다른 값" 이에요. 많이 쓰이는 PyYAML(YAML 1.1 규칙)으로 직접 확인해 봤어요.

import yaml
print(yaml.safe_load("""
country: NO
version: 1.10
port: 08
time: 12:30
on: yes
zip: 012345
"""))
# {'country': False, 'version': 1.1, 'port': '08', 'time': 750,
#  True: True, 'zip': 5349}

하나씩 보면 이래요.

쓴 값기대PyYAML 결과이유
NO국가 코드 문자열False1.1에서 yes/no/on/off 는 불리언
1.10버전 문자열1.1 (실수)숫자로 추론, 뒤 0 소실
12:30시각 문자열75060진수 정수(12×60+30)
on: (키)문자열 키True키도 불리언으로 변환
012345우편번호53490으로 시작하면 8진수
08문자열'08'8진수로 불가능해 문자열로 남음

012345 는 8진수로 읽히는데 08 은 문자열로 남는 일관성 없음이 특히 고약해요. 국가 코드 목록에서 노르웨이(NO)만 false 가 되는 사례가 유명해서 "노르웨이 문제"라고 불러요. YAML 1.2 규칙은 불리언을 true/false 로 좁혀서 이 문제를 줄였지만, PyYAML 같은 널리 쓰이는 파서가 여전히 1.1 규칙을 따르니 안심할 수 없어요.

대책은 단순해요.

헷갈릴 땐 YAML을 JSON으로 변환해 JSON 포맷터에 넣고 펼쳐 보세요. 들여쓰기 때문에 엉뚱한 부모에 붙은 키나, 문자열이어야 할 값이 숫자·불리언으로 바뀐 게 바로 드러나요.

XML이 여전히 쓰이는 곳과 주의점

"XML은 옛날 거"라고 넘기기엔 아직 현역인 곳이 많아요. 지금 보고 계신 이 글의 그림(SVG)도 XML이고, docx·xlsx 파일의 압축을 풀면 안쪽이 전부 XML이에요. RSS·Atom 피드, 안드로이드 레이아웃, Maven pom.xml, 공공기관·금융권의 오래된 SOAP 연동도 그렇고요. 네임스페이스와 XSD 스키마 검증이 성숙해서 형식을 엄격하게 지켜야 하는 문서형 데이터에는 여전히 강해요.

XML을 다룰 때 꼭 알아야 할 보안 이슈가 XXE(XML 외부 엔티티) 예요. DTD에 <!ENTITY x SYSTEM "file:///etc/passwd"> 를 넣으면 취약한 파서는 서버 파일을 읽어 응답에 넣어 버려요. 신뢰할 수 없는 XML을 받는다면 외부 엔티티를 끈 파서(파이썬은 defusedxml)를 쓰세요. 속성과 요소 중 무엇을 쓸지는 "값의 메타데이터(단위, 언어, id)는 속성, 내용은 요소"가 무난한 기준이에요.

상황별로 고르는 기준

상황추천이유
서비스 간 API, 프런트-백엔드 통신JSON속도·호환성·타입 명확
쿠버네티스, GitHub Actions 등YAML생태계 표준, 주석 필요
사람이 자주 고치는 앱 설정YAML 또는 TOML주석, 가독성
실수가 치명적인 단순 설정JSON 또는 TOML타입 추론 함정 없음
대량 로그, 이벤트 스트림JSON Lines한 줄 한 레코드, 스트리밍 쉬움
문서형 데이터, 기존 표준 연동XML스키마·네임스페이스

TOML은 표에 처음 등장하는데, pyproject.toml, Cargo.toml 에서 쓰는 포맷이에요. 주석이 되면서 타입이 명시적이라 "YAML의 편함 + JSON의 엄격함" 중간쯤이에요. 다만 깊게 중첩된 구조는 표현이 장황해져서 쿠버네티스 같은 곳엔 안 맞아요.

포맷 변환할 때 흔한 실수

포맷을 오가며 작업하다 보면 이런 데서 데이터가 조용히 바뀌어요.

  1. XML → JSON: 자식 요소가 1개일 땐 객체, 2개 이상일 땐 배열로 변환되는 라이브러리가 많아요. roles 가 어떤 사용자는 문자열, 어떤 사용자는 배열이 되는 식이죠. 변환 후에는 항상 배열로 강제하세요.
  2. YAML → JSON: 앞의 타입 추론이 그대로 굳어져요. NO 가 false 로 저장된 JSON이 만들어지는 거예요.
  3. JSON → YAML: 키 순서가 정렬되는 라이브러리가 있어요(PyYAML safe_dump 기본값 sort_keys=True). 사람이 읽는 순서를 지키려면 sort_keys=False.
  4. 큰 정수: JSON 숫자를 JS로 읽으면 2^53을 넘는 ID가 반올림돼요. 트위터·디스코드 API가 ID를 문자열로도 주는 이유예요.

마지막 항목은 포맷 문제가 아니라 파서의 숫자 타입 문제지만, 체감상 가장 늦게 발견되는 버그라 덧붙였어요. 문법 오류 자체는 JSON 파싱 에러 패턴 글에 따로 정리해 뒀어요.

자주 묻는 질문

JSON과 YAML 중 설정 파일에는 무엇이 더 좋나요?

사람이 자주 고치고 주석이 필요하면 YAML, 실수 하나가 치명적이고 기계가 주로 생성하면 JSON이 좋아요. YAML을 고른다면 문자열 따옴표 습관과 yamllint 같은 린터를 같이 도입하세요. 둘 다 애매하면 타입이 명시적인 TOML도 후보예요.

YAML에서 NO가 false로 바뀌는 이유는 무엇인가요?

YAML 1.1 규칙에서 yes, no, on, off, y, n 등을 불리언으로 해석하기 때문이에요. PyYAML 등 많이 쓰이는 파서가 1.1 규칙을 따라서 국가 코드 NO가 false로 읽혀요. "NO" 처럼 따옴표로 감싸면 해결돼요.

JSON은 YAML의 부분집합인가요?

YAML 1.2부터 사실상 그렇게 설계됐어요. 대부분의 JSON 문서는 YAML 파서로 그대로 읽혀요. 다만 아주 드문 이스케이프·중복 키 처리에서 차이가 있어서, JSON은 JSON 파서로 읽는 게 가장 안전해요.

XML은 이제 안 써도 되나요?

새 API를 설계한다면 JSON이 기본이지만, SVG, 오피스 문서, RSS, 안드로이드 레이아웃, 공공·금융 SOAP 연동처럼 XML이 표준인 영역은 계속 남아 있어요. 그런 곳과 연동한다면 XML을 피할 수 없고, 외부 엔티티를 끈 안전한 파서를 쓰는 게 중요해요.

JSON이 XML보다 항상 작은가요?

같은 데이터 1,000건 기준으로 요소형 XML은 압축 JSON의 1.49배였지만, 속성형 XML은 1.1배로 큰 차이가 없었어요. 오히려 들여쓴 JSON이 1.73배로 가장 컸어요. 실제 전송은 gzip 등으로 압축되니 체감 차이는 더 줄어들어요.

정리

세 포맷은 우열이 아니라 자리의 문제예요. 기계가 자주 읽는 곳엔 빠르고 엄격한 JSON, 사람이 자주 고치는 곳엔 주석이 되는 YAML(단, 따옴표와 린터로 타입 추론을 막기), 표준이 정해진 문서형 데이터엔 XML. 직접 재 보니 용량 차이는 생각보다 작고, 파싱 속도 차이는 생각보다 컸어요.

변환한 결과를 확인할 땐 JSON 포맷터로 구조를 펼쳐 보고, 문법만 빠르게 보려면 JSON 검증기를 쓰세요. API 응답이 이상할 때 확인 순서는 API 응답 디버깅 습관 글에 정리해 뒀어요.

#JSON YAML 차이#JSON XML 차이#YAML 노르웨이 문제#데이터 포맷 비교#설정 파일 포맷#YAML 문법 주의점
← 이전 글한글 한 글자 몇 바이트? UTF-8 3바이트·EUC-KR 2바이트 차이다음 글 →초·밀리초 타임스탬프 혼동 버그 — 1970년·5만 년이 찍히는 이유