JSON data types explained
Six types, and about a dozen edge cases that cause almost all real-world bugs.
Last reviewed
string
Double-quoted Unicode text. Characters outside the Basic Multilingual Plane — emoji, many CJK extension characters — are written as surrogate pairs when escaped, so a single emoji is two \uXXXX sequences. Any code that counts "characters" by counting UTF-16 units will get that wrong.
There is no separate date type. Dates are strings by convention, usually ISO 8601, and nothing enforces that.
number
One numeric type: an optionally signed decimal with an optional fraction and exponent. There is no separate integer type, and no defined precision or range.
In practice, precision is whatever the parser uses, and that is IEEE-754 double almost everywhere. Integers stay exact up to 9,007,199,254,740,991. Beyond that they round, silently — 9007199254740993 parses as ...992. This is the single most damaging JSON edge case, because nothing reports it and the value looks plausible.
The usual fix is to send large ids as strings. If you cannot change the producer, parse with a lossless parser that keeps the original token.
2^53 - 1 9007199254740991 exact
2^53 9007199254740992 exact
2^53 + 1 9007199254740993 parses as ...992boolean
true and false, lowercase, unquoted. "true" with quotes is a string, and a surprising amount of configuration breaks on exactly that difference.
null
The explicit empty value. It is not the same as a key being absent, and the difference usually matters: {"nickname": null} says there is no nickname; {} says nothing about nicknames at all. PATCH-style APIs depend on the distinction, and many client libraries erase it.
object
An unordered set of string keys to values. Key order is not meaningful, though most parsers preserve it. Duplicate keys are permitted by the grammar and undefined in behaviour — treat them as a bug.
Keys are always strings. {"1": "a"} has the string key "1", and a language that converts it to an integer key on parse has changed the data.
array
An ordered list. Order is meaningful, unlike object keys. Elements need not share a type, which is legal and is also what makes generated code for such an array awkward — a union, or the language’s "any", is usually the only honest result.
Mapping to languages
- TypeScript — string, number, boolean, null; large integers need string or bigint.
- Python — str, int/float, bool, None; json.loads gives arbitrary-precision ints, so Python is safer than JavaScript here.
- Java — String, Integer/Long/BigDecimal, Boolean, null; pick Long or BigInteger for large ids.
- Go — string, float64 by default (use json.Number to keep precision), bool, nil.
- C# — string, long/decimal, bool, null; System.Text.Json reads large integers as long correctly.
Keep reading
- What is JSON? — JSON is a text format for structured data. It has six value types, one page of grammar, and a handful of rules that trip people up constantly.
- JSON escape characters — Eight escape sequences plus \uXXXX. Everything else after a backslash is a syntax error.