BSON 디코더 · 확장 JSON 뷰어
BSON은 MongoDB가 저장하고 주고받는 이진 형식입니다. 리틀엔디언 int32 길이로 시작해서, 타입 코드 한 바이트와 NUL로 끝나는 필드 이름, 그리고 타입이 길이를 결정하는 값이 이어지고, 마지막에 0 바이트 하나로 닫힙니다. 이 도구는 그 바이트를 브라우저 안에서 직접 읽어 세 가지 방식으로 동시에 보여 줍니다. 요소마다 타입 이름과 타입 바이트, 차지하는 바이트 수를 적은 필드 트리, 정규 확장 JSON, 완화 확장 JSON입니다. 명세가 정의한 21가지 요소 타입을 모두 처리하며, 옛 데이터에서만 만나게 되는 폐기된 타입 — undefined(0x06), DBPointer(0x0C), symbol(0x0E), 그리고 페이로드 안에 두 번째 길이를 품고 있어서 잊어버린 리더에게 쓸모없는 4바이트를 얹어 주는 바이너리 서브타입 0x02 — 도 포함합니다. 브라우저 기반 디코더가 가장 자주 틀리는 곳은 숫자입니다. int64와 UTC 날짜, MongoDB 내부 timestamp는 모두 64비트인데 JavaScript 숫자는 정수를 53비트까지만 정확히 담습니다. 그래서 9223372036854775807은 한 번만 거쳐도 9223372036854775808이 되어 버립니다. 여기서는 셋 다 BigInt로 읽고 BigInt에서 바로 출력합니다. decimal128은 128비트 계수와 편향 지수에서 곧바로 정확한 십진 문자열로 풀어내며, double을 거치지 않습니다. 돈을 다루려고 만든 타입이니 그래야 의미가 있습니다. 트리는 ObjectId를 실제 구성 요소 셋 — 4바이트 생성 시각, 5바이트 난수, 3바이트 카운터 — 으로 펼쳐 주고, UTC 날짜는 원본 부호 있는 밀리초 값과 날짜를 나란히 보여 주므로 1970년 이전 날짜의 음수가 더 이상 놀랍지 않습니다. 대신 추측은 하지 않습니다. 선언한 길이가 파일과 맞지 않거나, 종료 바이트에 닿지 못하거나, 명세에 없는 타입 바이트가 있거나, 실수로 넣은 JSON 파일이라면, 그럴듯한 트리로 반쯤 해석하는 대신 오류 이름과 바이트 위치를 밝히며 거부합니다.
사용법
- .bson 파일을 상자에 놓으세요. mongodump 결과물, 드라이버가 저장한 단일 문서, MongoDB 도구가 만든 파일이면 됩니다.
- 트리를 읽으세요. 각 행이 요소 하나이며 BSON 타입 이름, 실제 전송되는 타입 바이트, 값, 차지하는 바이트가 함께 나오므로 문서가 왜 커졌는지 스스로 설명해 줍니다.
- 타입을 지켜야 한다면 정규 확장 JSON을 쓰세요. $numberInt와 $numberLong은 다른 값입니다. 그냥 읽기만 할 때는 완화 확장 JSON이 편합니다.
- ObjectId 행 아래 줄에서 생성 시각과 난수, 카운터를 확인하고, UTC 날짜 행 아래 줄에서 원본 밀리초에 해당하는 날짜를 확인하세요.
- 파일이 거부되면 어떤 오류인지 보세요. 길이 불일치, 종료 바이트 없음, 정의되지 않은 타입 바이트는 서로 다른 문제이며 바이트 위치가 어디를 봐야 할지 알려 줍니다.
자주 묻는 질문
- 같은 배열인데 왜 BSON이 JSON보다 훨씬 큰가요?
- BSON에는 배열 전용 타입이 없기 때문입니다. 배열은 필드 이름이 십진 인덱스 "0", "1", "2" … 인 평범한 문서로 저장되고, 그 인덱스는 실제로 NUL로 끝나는 문자열로 전송됩니다. 그래서 32비트 정수 1000개는 값만 4KB인데 타입 바이트, 한두 세 자리 인덱스 이름, 종료 바이트까지 더해 대략 8.9KB가 됩니다. 인덱스 키가 틀려도 아무도 눈치채지 못하는 이유이기도 합니다. 드라이버는 배열을 위치로 읽으므로 키가 "0", "2", "x"인 배열도 요소 세 개로 잘 풀리고, 무언가가 다시 직렬화할 때에야 문제가 드러납니다. 이 도구는 그런 배열을 조용히 넘기지 않고 경고로 알려 줍니다.
- 정규 확장 JSON과 완화 확장 JSON은 무엇이 다른가요?
- 확장 JSON은 BSON을 텍스트로 적는 표준 방식이고, 쓰임이 둘이라 형식도 둘입니다. 정규 형식은 모든 값을 타입 표식으로 감쌉니다. {"$numberInt": "1"}과 {"$numberLong": "1"}, {"$numberDouble": "1.0"}은 서로 다른 값이고, 그래서 문서를 텍스트로 내보냈다가 똑같은 바이트로 되돌릴 수 있습니다. 완화 형식은 평범한 JSON으로 담을 수 있는 값에서는 표식을 벗깁니다. 숫자는 숫자가 되고, ISO 8601 범위 안의 날짜는 읽을 수 있는 시각이 됩니다. mongoexport의 기본값이고 훨씬 읽기 좋지만 손실이 있습니다. 1이 그냥 1이 되고 나면 세 가지 숫자 타입 중 무엇이었는지 알 길이 없습니다.
- JavaScript가 표현하지 못하는 BSON 값은 무엇이고, 여기서는 어떻게 처리하나요?
- 셋입니다. int64와 UTC 날짜는 부호 있는 64비트 정수이고, 내부 timestamp는 64비트 한 워드에 부호 없는 32비트 둘이 들어 있습니다. 반면 JavaScript 숫자는 double이라 정수는 9,007,199,254,740,991까지만 정확합니다. 그보다 크면 들어오는 순간 반올림되고, 그래서 수많은 뷰어에서 9223372036854775807이 9223372036854775808로 바뀝니다. 이 디코더는 셋 다 BigInt로 읽고 BigInt에서 자릿수를 출력하므로 double을 거치는 일이 없습니다. decimal128은 더 심각합니다. 0.1과 2.675를 정확히 유지하려고 만든 십진 타입인데 double로 읽어 버리면 선택한 이유 자체가 사라집니다. 여기서는 128비트 계수와 편향 지수에서 곧장 십진 문자열로 풉니다.
- 1970년 이전 날짜가 왜 음수로 보이나요?
- BSON이 그렇게 저장하기 때문입니다. UTC 날짜 타입은 유닉스 에포크 이후 밀리초를 int64로 담고 그 정수는 부호가 있습니다. 그래서 1970-01-01보다 앞선 시각은 모두 음수이고, 1953년 생일은 대략 −526,608,000,000입니다. 적법한 값이라 어떤 드라이버든 그대로 왕복시키지만, 이 필드를 부호 없는 값으로 다루거나 0에서 잘라 버리는 도구가 의외로 많습니다. 1953년이 1970년이 되거나 2억 9200만 년으로 튀는 것이 그 결과입니다. 이 타입이 담을 수 있는 범위는 대부분의 소프트웨어가 지원하는 달력보다 훨씬 넓기 때문에, 이 도구는 원본 밀리초를 날짜 옆에 같이 보여 주고 달력으로 표현할 수 없는 값은 그렇다고 분명히 말합니다.
- 바이너리 서브타입 0x02는 무엇이고 왜 함정이라고 하나요?
- 지금은 폐기된 최초의 바이너리 배치입니다. 바이너리 필드는 int32 길이, 서브타입 한 바이트, 그다음 페이로드로 쓰이는데 서브타입 0x02만은 페이로드가 두 번째 int32로 시작하고 그 값은 바깥 길이보다 정확히 4 작습니다. 다른 서브타입과 똑같이 다루는 리더는 앞에 쓸모없는 4바이트가 붙은 덩어리를 돌려주고, 그 바이트도 그럴듯한 이진 데이터처럼 보이기 때문에 아무도 불평하지 않습니다. 문제는 그 값을 실제로 쓰려 할 때에야 드러납니다. 2.0 이전 시절의 오래된 컬렉션에는 아직 이런 값이 남아 있어서, BSON을 읽는다고 말하는 디코더라면 이 경우를 따로 처리해야 합니다. 이 도구는 그렇게 하고, 안쪽 길이가 바깥과 맞지 않으면 그 필드를 아예 거부합니다.
- ObjectId 안에는 무엇이 들어 있나요? UUID인가요?
- 아닙니다. 구조가 있는 12바이트이고, 보통은 그 구조를 알고 싶어서 들여다봅니다. 앞 4바이트는 빅엔디언 유닉스 초 단위 타임스탬프입니다. _id로 정렬하면 대략 생성 순서가 되고 별도 필드 없이 날짜로 걸러낼 수 있는 이유가 이것입니다. 다음 5바이트는 프로세스마다 정해지는 난수이고, 마지막 3바이트는 임의의 값에서 시작해 문서마다 1씩 늘어나는 카운터입니다. 예전 버전은 가운데에 머신 식별자와 프로세스 ID를 넣어 서버 정보를 조금 흘렸는데, 3.4에서 바뀌었습니다. 체크섬도 버전 필드도 없어서 16진수 24자리면 문법적으로는 모두 올바른 ObjectId이고, 그럴듯한 값인지 판단할 근거는 안에 박힌 타임스탬프뿐입니다.
관련 도구
Hex Dump 뷰어
텍스트 또는 작은 파일을 오프셋 + hex 바이트 + 인쇄 가능 ASCII로 표시. `xxd`나 `hexdump -C` 형식.
JSON to Mongoose 스키마 생성기
JSON 객체를 타입이 매핑된 경로가 포함된 Mongoose 스키마로 브라우저에서 변환합니다.
Java .properties 파서
java.util.Properties.load와 똑같이 .properties 파일을 파싱하고, 구분자·이스케이프·이어붙이기에서 놀랄 만한 줄을 짚어 줍니다.
ULID 디코더 (타임스탬프 & 랜덤성)
ULID를 48비트 생성 타임스탬프와 80비트 랜덤성으로 디코딩 — Crockford Base32, 대소문자 무관, 시간을 ISO UTC·상대·에폭 밀리초로 표시 — 브라우저에서.
MessagePack 디코더
base64나 hex로 된 MessagePack 바이트를 붙여넣으면 타입, 중첩 맵, 바이너리, 타임스탬프까지 브라우저에서 해석합니다.
ASCII 코드 표
256행 ASCII / 확장 ASCII 표 — 10진·16진·8진·2진 + 제어 문자 이름. 검색·복사.