JSON file format
JavaScript Object Notation, a lightweight structured data format.
- MIME type
- application/json
- Extensions
- .json
- Category
- Text & markup
Magic-number signature
Text starting with '{' or '[' followed by a quoted key or nested bracket.
About the JSON format
JSON is the subset of JavaScript object-literal syntax that Douglas Crockford wrote down rather than invented, and the whole specification fits comfortably on a few pages. It has six types - object, array, string, number, boolean and null - and no date, no binary, no comment and no version. Every one of those omissions is deliberate, and every one of them is worked around somewhere: dates become ISO strings, binary becomes base64, comments become a key nobody reads, versioning becomes a field the reader has to remember to check. That combination of a tiny grammar and universal support is why it won, and it is also why almost every practical JSON problem is about something the format does not say.
Header layout, byte by byte
| Offset | Length | Field | Meaning |
|---|---|---|---|
| 0 | 1 | First structural character | 7B for an object or 5B for an array, after any leading whitespace. The current standard allows a JSON text to be any value at all, so a bare number or a quoted string is legal too, but the superseded 2006 specification permitted only objects and arrays and virtually every producer alive still emits one of those two. |
| 0 | 4 | Encoding-detection window | Because the first two characters of a JSON text are always ASCII, the pattern of nulls in the first four bytes identifies the encoding: 00 00 00 xx is UTF-32 big-endian, 00 xx 00 xx is UTF-16 big-endian, xx 00 00 00 is UTF-32 little-endian, xx 00 xx 00 is UTF-16 little-endian, and four non-null bytes are UTF-8. Modern JSON must be UTF-8 on the wire, so in practice this test tells you what went wrong rather than what to do. |
| 0 | 3 | UTF-8 byte-order mark | EF BB BF. The standard says an implementation must not add one and permits a parser to ignore one; the JavaScript engines behind Node and the browsers do neither and throw on it, which makes a mark added by a Windows editor a routine reason a hand-edited config file stops loading with no visible change to its text. |
| 3 | 1 | First structural character behind a mark | Where the document really begins when a mark is present. Worth stating on its own, because the error a parser reports names an unexpected token at position 0 and the character at position 0 is invisible in every editor that will be used to look for it. |
| 0 | variable | Everything else | There is no header. No magic number, no length, no version, no checksum, no trailer, no index. A JSON file is content from its first byte to its last, and every property a reader might want to know - how big it is, whether it is complete, what shape it has - can only be obtained by parsing all of it. |
Telling it apart from its neighbours
| Easily confused with | How to tell |
|---|---|
| NDJSON/JSON Lines | Byte-identical at the front - an opening brace either way. The difference is that JSON Lines puts one complete document on each line, with no enclosing array and no commas between them, so a standard parser reads the first line successfully and then fails on the second with an unexpected character after the end of the JSON. Log pipelines and dataset dumps use it precisely because it can be appended to and read one line at a time. |
| JSONC / JSON5 | Also identical at offset 0, and invalid the moment you reach the first // comment or trailing comma. tsconfig.json and the settings files of several editors are JSONC and are not JSON, which is why a program that reads them with a strict parser fails on a file the editor that wrote it accepts without comment. |
| XML | A brace or a bracket against an angle bracket, so telling them apart is trivial. The comparison that actually matters is what they are for: JSON has exactly one way to express a list and no way at all to express text with markup inside it, which is why data moved to JSON and documents did not. |
| CBOR/MessagePack/BSON | Binary encodings of nearly the same data model, and none of them are text. CBOR writes a map as a single leading byte in the range A0 to BF; BSON begins with a little-endian 32-bit total length, so its first byte is usually small and unprintable. If a file that should be JSON opens as binary, one of these is the likely answer and nothing at all is damaged. |
| JSONP | A callback name and an opening parenthesis in front of the object - an executable JavaScript statement rather than a data document. The rule is simple enough to apply from the first byte: anything starting with a letter is not JSON. |
How these files break
- Truncation. There is no length and no trailer, so a document cut short fails with an unexpected end of input and no way to tell how much is missing. Streams written without a final flush lose the closing brace and nothing else, which is why the repair that works most often is simply appending one.
- A byte-order mark. Three bytes no editor displays, in front of a file that is otherwise perfect, producing a parse error that names position 0. Checking the first three bytes in a hex viewer settles in seconds what can otherwise cost an afternoon.
- Trailing data. Two documents concatenated, or a log line appended to a file, gives an unexpected non-whitespace character after the JSON at a stated position - and that position is counted in characters of the decoded text, not in bytes, so on any file containing non-ASCII it will not line up with a hex editor's cursor.
- Numbers that come back as different numbers. The format has one number type and states no precision, and readers built on IEEE 754 doubles agree exactly only on integers between minus 2 to the 53rd plus one and 2 to the 53rd minus one. An identifier of 9007199254740993 read and written back becomes 9007199254740992, with no error raised anywhere: the bytes on disk were right and the value in memory was not. Systems that hit this ship identifiers as strings from then on.
- Lone surrogates. A string containing an escape such as \uD800 with no pairing escape after it parses successfully, because the grammar accepts any four hex digits, and then cannot be written back out as valid UTF-8. What emerges on the far side is a replacement character, so the data is quietly different after a round trip that reported success at every step.
Where the format stands today
JSON is the default data format of everything networked, and its position is not seriously contested. REST and GraphQL both speak it, package manifests and lock files are written in it, log pipelines emit a document per line, and every language ships a parser in its standard library. Where it is deliberately not used is more telling than where it is. Configuration keeps drifting to YAML and TOML because JSON has no comments and no trailing commas, which are ergonomic complaints rather than technical ones and are none the less decisive. High-volume internal traffic moves to Protocol Buffers, CBOR or MessagePack, because parsing text costs more than parsing a length-prefixed binary and having a schema is worth the build step. Very large tabular data goes to Parquet, because JSON repeats every key on every row. What keeps JSON first choice is that a person can read it: for an API someone else has to integrate against, for a config file someone will edit at three in the morning, and for anything that will be debugged by looking at it, the inefficiency is simply the price of being self-explanatory in a terminal.
Open JSON files on this site
Every one of these runs in your browser — the file never leaves your machine.
Related formats
Formats that are easy to confuse with JSON — compare their leading bytes.