새 프로젝트 설정 파일 포맷을 정하는 회의에서 "그냥 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은 여는 태그와 닫는 태그가 쌍을 이루고, 값을 속성(<user name="smith">)으로 둘지 자식 요소로 둘지도 설계자가 정해야 해요.
참고로 YAML 1.2는 JSON을 거의 그대로 포함하도록 설계돼서, 대부분의 JSON 문서는 YAML 파서로도 읽혀요. "YAML은 JSON의 상위 집합"이라는 말이 여기서 나왔어요.
한눈에 비교표
| 항목 | JSON | YAML | XML |
|---|---|---|---|
| 표준 | RFC 8259 / ECMA-404 | YAML 1.2 (2009), 많은 파서는 1.1 | W3C XML 1.0 |
| 주석 | 불가 | 가능 (#) | 가능 (<!-- -->) |
| 데이터 타입 | 문자열·숫자·불리언·null·배열·객체 | JSON 타입 + 날짜, 앵커/참조 등 | 전부 문자열 (스키마로 타입 부여) |
| 손으로 쓰기 | 보통 (쉼표·따옴표 실수) | 편함 (들여쓰기 실수) | 번거로움 |
| 파싱 속도 | 매우 빠름 | 느림 | 빠름 |
| 스키마 검증 | JSON Schema | JSON 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은 모든 필드를 자식 요소로 둔 버전과, 단순 필드를 속성으로 둔 버전 두 가지로 만들었어요.
결과가 흥미로워요. "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)))
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 | 국가 코드 문자열 | False | 1.1에서 yes/no/on/off 는 불리언 |
1.10 | 버전 문자열 | 1.1 (실수) | 숫자로 추론, 뒤 0 소실 |
12:30 | 시각 문자열 | 750 | 60진수 정수(12×60+30) |
on: (키) | 문자열 키 | True | 키도 불리언으로 변환 |
012345 | 우편번호 | 5349 | 0으로 시작하면 8진수 |
08 | 문자열 | '08' | 8진수로 불가능해 문자열로 남음 |
012345 는 8진수로 읽히는데 08 은 문자열로 남는 일관성 없음이 특히 고약해요. 국가 코드 목록에서 노르웨이(NO)만 false 가 되는 사례가 유명해서 "노르웨이 문제"라고 불러요. YAML 1.2 규칙은 불리언을 true/false 로 좁혀서 이 문제를 줄였지만, PyYAML 같은 널리 쓰이는 파서가 여전히 1.1 규칙을 따르니 안심할 수 없어요.
대책은 단순해요.
- 숫자로 계산할 값이 아니면 문자열은 무조건 따옴표로 감싸기 (
version: "1.10",zip: "012345") yamllint같은 린터를 CI에 넣기 (truthy 값 경고 규칙이 있어요)- 들여쓰기는 공백만 사용 — 탭이 섞이면
found character '\t' that cannot start any token에러가 나요 - 파이썬에서는
yaml.load()대신 반드시yaml.safe_load()— 전체 로더는 YAML 태그로 임의 객체를 만들 수 있어 신뢰할 수 없는 입력에 위험해요
헷갈릴 땐 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의 엄격함" 중간쯤이에요. 다만 깊게 중첩된 구조는 표현이 장황해져서 쿠버네티스 같은 곳엔 안 맞아요.
포맷 변환할 때 흔한 실수
포맷을 오가며 작업하다 보면 이런 데서 데이터가 조용히 바뀌어요.
- XML → JSON: 자식 요소가 1개일 땐 객체, 2개 이상일 땐 배열로 변환되는 라이브러리가 많아요.
roles가 어떤 사용자는 문자열, 어떤 사용자는 배열이 되는 식이죠. 변환 후에는 항상 배열로 강제하세요. - YAML → JSON: 앞의 타입 추론이 그대로 굳어져요.
NO가false로 저장된 JSON이 만들어지는 거예요. - JSON → YAML: 키 순서가 정렬되는 라이브러리가 있어요(PyYAML
safe_dump기본값sort_keys=True). 사람이 읽는 순서를 지키려면sort_keys=False. - 큰 정수: 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 응답 디버깅 습관 글에 정리해 뒀어요.