Three notations, one data model
BSON, JSON and TOON all describe the same thing: objects, arrays, strings, numbers, booleans and null. They agree about almost nothing else — BSON is binary, length-prefixed and typed, JSON is text with exactly six types, and TOON trades JSON's punctuation for indentation and a CSV-style table so a document costs fewer tokens in an LLM prompt.
{"_id": {"$oid": "6a69eb40a1b2c3d4e5f60718"}'} — Extended JSON is how an ObjectId survives a format that only knows strings and numbers
So this tool never rewrites one notation into another. It reads whichever you paste into one plain value and writes that value out in whichever you asked for, which is why BSON → TOON cannot develop its own opinion about what a date is. Where the model runs out is BSON's type system — it knows an ObjectId from a string and an int32 from a double — and the bridge is Extended JSON, which travels through TOON unchanged. Nothing is uploaded: reading, writing and export all happen in this tab.
Canonical and relaxed
Extended JSON has two spellings, and the difference is not cosmetic. Canonical writes every value in a $-prefixed wrapper naming its type, and a document read canonically and written back produces the same bytes it came from — every type, byte for byte. Relaxed writes what plain JSON can hold and wraps only what it cannot, which reads far better and quietly loses the distinctions plain JSON has no room for.
| BSON type | Canonical | Relaxed | What relaxed costs |
|---|---|---|---|
| Double | {"$numberDouble":"1.0"} | 1 | A double holding a whole number returns as an int32. |
| Int64 | {"$numberLong":"9007199254740993"} | 9007199254740992 | Past ±2⁵³ the value itself changes — no JavaScript number holds it. |
| Date | {"$date":{"$numberLong":"1577934245678"}} | {"$date":"2020-01-02T03:04:05.678Z"} | Nothing. The ISO string round-trips exactly. |
| ObjectId | {"$oid":"64b7c0f0e1a2b3c4d5e6f708"} | {"$oid":"64b7c0f0e1a2b3c4d5e6f708"} | Nothing. Relaxed wraps it too. |
| Decimal128 | {"$numberDecimal":"19.99"} | {"$numberDecimal":"19.99"} | Nothing. Decimal128 has no plain-JSON spelling. |
| Binary | {"$binary":{"base64":"AQID","subType":"00"}} | {"$binary":{"base64":"AQID","subType":"00"}} | Nothing. Binary is wrapped in both modes. |
Rather than warn about this in general, the tool checks it on your document: after a relaxed read it writes the result back to BSON and compares the bytes. If they differ you get a note saying so; if they do not, you get silence. Most documents survive relaxed intact, and a warning that is usually wrong is a warning people stop reading.
Going the other way there is nothing to choose. Both spellings parse to the same bytes, so the mode control is switched off whenever BSON is the output rather than the input.
Why TOON is shorter
TOON picks a rendering from the shape of the value, and the one that pays is the table. A uniform array of objects declares its field list once in the header and then streams one row per element, which is the whole point of the format.
| Form | Chosen when | Looks like |
|---|---|---|
| Inline | An array of primitives, short enough to sit on its own header line. | tags[2]: red,blue |
| Tabular | An array of objects that all carry the same fields, with primitive values. | users[2]{id,name}:
1,Ada
2,Bob |
| Keyed tabular | An object whose values are uniform objects — config maps, feature flags, records by id. | envs[2:]{region,replicas}:
production: eu-1,6
staging: eu-1,2 |
| List | Anything else: mixed types, or objects that do not agree on their fields. | items[2]: - 1 - name: Ada |
Which is why TOON is not always smaller. A deeply nested document with no uniform arrays encodes to roughly what minified JSON does, occasionally a little more. The counts under the output say which happened for the document in front of you — characters rather than tokens, because a tokenizer is a megabyte of vocabulary and gives a different answer for every model.
The controls
| Option | What it does | Reach for it when |
|---|---|---|
| BSON notation | Whether BSON is read and written as hexadecimal or as base64. | Hex for anything copied out of bsondump or the MongoDB documentation; base64 for a blob from a driver log or an API response. |
| Extended JSON | Whether BSON's types are written as canonical wrappers or as the nearest plain JSON value. | Canonical whenever the document has to survive the trip back. Relaxed when a person is going to read it. |
| JSON indent | Spaces per level in the JSON output, or one unbroken line. | Minified for anything a program will read; two or four spaces for anything a person will. |
| TOON delimiter | Which character separates TOON's table columns. | A tab or a pipe when no value contains one — the encoder quotes any value that does, which costs more than the comma saved. |
| TOON indent | Spaces per level in TOON, on the way in as well as out. | Leave it at two unless the block you are pasting was written with four. |
| Check declared lengths | Whether a declared array length has to match the number of rows under it. | Leave it on. Turn it off only to read a block edited by hand, or truncated by a model that stopped early. |
Each control is switched off when the current pairing cannot read it, with a line saying why. Nudging the TOON delimiter while the output is BSON changes nothing, and a control that looks live but is not is worse than one that is greyed out.
Traps worth knowing
BSON's root is a document. An array or a bare value has no encoding at the top level, and the driver refuses rather than inventing one — so a JSON document or TOON block whose root is an array cannot be written as BSON. Give it a name first, and the wrapper object is what you convert.
A hex dump is not a .bson file. The text output downloads as .txt with the notation in its name; the raw bytes get their own button and the real extension. Naming a transcript .bson produces a file every reader of BSON rejects.
JSON has one number type. Going into BSON, a whole number becomes an int32 or an int64 and a fractional one becomes a double — which means 1.0 and 1 are the same input, and both come back as 1. Canonical Extended JSON is the only way to say “a double, holding one”.
Relaxed dates are still objects. A relaxed date is written as a string inside a $date wrapper, which anything that reads Extended JSON understands, but a program doing a plain JSON.parse sees an object with one string in it. That is a property of Extended JSON, not of this tool.
TOON's indentation is load-bearing. Unlike JSON, whitespace is structure: a block written at four spaces and read at two is a parse error rather than a shrug. That is what the indent control is for on the input side, and why it stays available when TOON is what you are pasting.
What people use this for
Reading a BSON blob out of a log, an oplog entry or a driver trace and seeing what is actually in it, with the types intact rather than flattened into JSON's six.
Turning a JSON fixture into TOON to cut the token cost of a prompt. Most of the saving comes from uniform arrays of records, which is exactly the shape fixtures and API responses tend to have.
Building a BSON document by hand from Extended JSON to feed a test, and getting the exact bytes rather than whatever a driver decided the numbers ought to be.
