CBOR는 JSON을 무엇으로 바꾸는가
같은 데이터를 95바이트에서 61바이트로 줄이고 바이트열과 큰 정수를 그대로 담습니다. JSON과 무엇이 다르고 어디에 쓰는지 정리했습니다.
JSON은 사람이 읽을 수 있고 어디서나 파싱됩니다. 대신 숫자도 불리언도 전부 글자로 적고, 바이트 데이터는 담을 방법이 없어 base64 문자열로 감싸야 합니다. CBOR(RFC 8949)는 같은 데이터 모델을 바이너리로 적습니다.
크기부터
센서 데이터 하나를 두 방식으로 인코딩하면 이렇습니다.
{"id":10231,"name":"sensor-a","ok":true,"temp":21.5,
"tags":["indoor","floor2"],"ts":1755500000}
| 방식 | 크기 |
|---|---|
| JSON (공백 제거) | 95바이트 |
| CBOR | 61바이트 |
35.8% 줄었습니다. 줄어든 자리는 대부분 따옴표, 콜론, 쉼표, 그리고 숫자를 글자로 적던 부분입니다.
값 하나가 몇 바이트인가
CBOR는 값 앞에 헤드 바이트를 하나 붙이고, 작은 값은 그 안에 밀어 넣습니다.
0 -> 00 (1바이트)
23 -> 17 (1바이트)
24 -> 1818 (2바이트)
255 -> 18ff (2바이트)
256 -> 190100 (3바이트)
65536 -> 1a00010000 (5바이트)
0부터 23까지는 헤드 바이트 하나로 끝납니다. 배열 길이, 맵 크기, 작은 정수가 대부분 이 범위라 실제 문서에서 자주 걸립니다.
JSON이 담지 못하는 것
바이트열. CBOR에는 바이트 문자열 타입이 따로 있습니다.
바이트 4개(deadbeef) -> 44deadbeef (5바이트)
JSON -> "3q2+7w==" 로 감싸야 함 (10바이트)
base64는 원본의 4/3 크기가 되고, 받는 쪽이 그 문자열을 다시 디코딩해야 한다는 약속을 따로 알아야 합니다. CBOR는 타입에 그 정보가 들어 있습니다.
64비트 정수. JSON.stringify(12345678901234567890n)은 예외를 던집니다.
TypeError: Do not know how to serialize a BigInt
CBOR에서는 1bab54a98ceb1f0ad2 9바이트입니다. JSON에서 큰 정수를 다루려면 문자열로 바꿔 보내고 받는 쪽에서 되돌리는 규약을 만들어야 합니다.
맵 키 순서가 바이트에 남는다
같은 내용이라도 키를 적은 순서가 다르면 바이트가 다릅니다.
{a:1, b:2} -> a2616101616202
{b:2, a:1} -> a2616202616101
서명이나 해시를 붙일 때 이게 문제가 됩니다. 보내는 쪽과 받는 쪽이 같은 순서로 만들지 않으면 값이 달라집니다. CBOR에는 이를 위해 결정적 인코딩 규칙이 정의돼 있습니다 — 맵 키를 정해진 순서로 정렬하고, 정수는 가장 짧은 형태로 적고, 부동소수점도 최단 표현을 씁니다. 서명 대상이라면 이 규칙을 쓰는 라이브러리인지 확인해야 합니다.
어디에 쓰나
- COSE — JWT의 CBOR 판입니다. WebAuthn과 패스키가 이 형식을 씁니다
- CTAP — 보안 키와 브라우저가 주고받는 프로토콜
- CoAP — 제약된 기기용 프로토콜. 패킷 하나에 들어가야 하므로 크기가 중요합니다
- DNS-over-HTTPS 캐시, 인증서 투명성 로그 등 바이트를 자주 담는 자리
반대로 브라우저 콘솔에서 눈으로 확인해야 하거나, 로그로 남겨 grep으로 찾아야 하거나, 프론트엔드와 주고받는 API라면 JSON이 그대로 낫습니다. 35%는 그 불편을 감수할 만큼 큰 차이가 아닙니다.
MessagePack과의 관계
비슷한 시기에 나온 비슷한 형식입니다. 차이는 표준화 상태입니다. CBOR는 IETF 표준(RFC 8949)이고 태그로 확장할 수 있는 체계가 정의돼 있습니다. MessagePack은 사실상 표준이고 구현이 조금 더 많습니다. 새로 고른다면 표준 문서가 있고 COSE가 얹혀 있는 쪽이 다루기 편합니다.
확인 순서
- 받은 바이트의 첫 바이트를 봅니다.
a1b7이면 맵,8097이면 배열입니다 - 디코딩이 실패하면 앞뒤에 다른 것이 붙어 있는지 봅니다. HTTP 응답이면 헤더가 섞였을 수 있습니다
- 서명을 검증한다면 결정적 인코딩을 쓰는지 확인합니다. 맵 키 순서가 다르면 같은 데이터도 서명이 깨집니다
- 크기를 비교할 때는 압축 후로 비교합니다. gzip을 거치면 JSON의 반복되는 키 이름이 상당 부분 줄어들어 격차가 좁아집니다
tools.onuel.dev의 CBOR 인코딩·디코딩에 JSON을 넣으면 CBOR 바이트와 크기를 함께 보여 줍니다. 나온 바이트를 Hex 덤프에 넣으면 헤드 바이트가 어디서 끊기는지 확인할 수 있고, 원본 JSON은 JSON 포매터에서 정리해 두고 비교하면 됩니다.
- #CBOR
- #직렬화
- #JSON