JSON looks simple, and its type system genuinely is: there are only six data types, and no way to define your own. But that simplicity hides a handful of rules that trip up experienced developers — no NaN, no trailing commas, no comments, no native date type, and integer precision that quietly breaks past a certain size.
This guide walks through all six JSON data types with runnable examples, shows how each maps to JavaScript and Python, and covers the gotchas that actually cause bugs: big-integer precision loss, dates smuggled in as strings, and the difference between null and a missing key. To eyeball a payload's structure as you read, paste it into the JSON Formatter.
The Six JSON Data Types at a Glance
The JSON specification (defined by ECMA-404 and RFC 8259) recognizes exactly six value types. Every valid JSON value is one of these — there is no seventh, and no mechanism to register custom types. Two are structural (object and array); four are primitives.
| Type | Example | Category |
|---|---|---|
| string | "hello" | primitive |
| number | 42, -3.14, 6.02e23 | primitive |
| boolean | true, false | primitive |
| null | null | primitive |
| object | { "id": 1 } | structural |
| array | [1, 2, 3] | structural |
A JSON document as a whole is a single value — most often an object or array, but a bare string or number is technically valid JSON too. Everything else you might reach for (dates, sets, functions, undefined, big integers, binary blobs) has to be encoded on top of these six primitives, usually as a string. That constraint is the source of most JSON surprises, so it is worth internalizing early.
{
"name": "Ada",
"age": 36,
"active": true,
"nickname": null,
"roles": ["admin", "editor"],
"address": { "city": "London", "zip": "EC1A" }
}Strings, Numbers, Booleans, and Null
Strings
A JSON string is a sequence of Unicode characters wrapped in double quotes. Single quotes are never allowed — a common mistake for developers coming from Python or JavaScript. Certain characters must be escaped with a backslash: the double quote (\"), the backslash itself (\\), and control characters like newline (\n), tab (\t), and carriage return (\r). Any character can also be written as a \uXXXX Unicode escape.
Pasting a string that contains quotes or newlines into a JSON field by hand makes escaping easy to get wrong. The JSON Escape / Unescape tool handles it both ways, and How to Escape and Unescape JSON Strings covers the rules in depth.
Numbers
JSON numbers are base-10 and cover both integers and floats in one type — the format itself has no separate int/float distinction. You can use a leading minus sign, a decimal point, and scientific notation (1.5e-9). What you cannot use: leading zeros (007), a leading plus sign, hexadecimal (0xFF), or a trailing decimal point.
Booleans and null
Booleans are the bare lowercase literals true and false — never quoted, never capitalized. null is its own distinct type representing an intentional absence of value. It is the only value of its type, and it is different from a key being absent entirely (more on that below).
Objects and Arrays: Structure and Nesting
Objects
An object is an unordered collection of key/value pairs wrapped in curly braces. Keys must be strings in double quotes — numbers or bare identifiers as keys are invalid. Values can be any of the six types, which is what makes JSON recursive. The spec does not forbid duplicate keys, but their behavior is undefined; most parsers keep the last one. Treat duplicate keys as a bug.
Arrays
An array is an ordered list of values in square brackets. Unlike arrays in statically typed languages, JSON arrays are heterogeneous — one array can mix strings, numbers, objects, and even more arrays. Order is significant and preserved by every conformant parser.
{
"users": [
{ "id": 1, "tags": ["a", "b"] },
{ "id": 2, "tags": [] }
],
"mixed": [1, "two", true, null, { "k": "v" }, [9, 8]]
}The spec sets no hard nesting limit, but parsers impose practical depth limits to prevent stack overflows, and deeply nested structures are hard to read. When a payload gets unwieldy, the JSON Formatter will indent it so the nesting is visible at a glance.
What JSON Deliberately Leaves Out
Much of what makes JSON confusing is what it does not have. Knowing the omissions upfront saves debugging time:
- No date or time type. Dates are conventionally encoded as ISO 8601 strings (
"2026-07-12T14:30:00Z"). The parser sees a plain string and will not revive it into a date object for you. - No comments.
//and/* */are not valid JSON. For commented config, reach for JSON5, JSONC, or NDJSON, not JSON. - No `undefined`. JavaScript's
undefinedis not a JSON value.JSON.stringifydrops object keys whose value isundefinedand turnsundefinedarray elements intonull. - No integer/float distinction, no BigInt. There is one
numbertype and no way to mark a value as an integer versus a decimal. - No trailing commas, no single quotes, no hex or octal. The grammar is strict.
- No binary type. Binary data is typically Base64-encoded into a string.
Because these constraints are enforced by the grammar rather than by convention, a quick pass through a JSON Validator will catch violations before they reach your application.
How JSON Types Map to JavaScript and Python
JSON's type names come from JavaScript, so the JS mapping is nearly one-to-one. Python's json module maps types just as cleanly, with a few naming differences.
| JSON | JavaScript | Python |
|---|---|---|
| string | string | str |
| number | number | int or float |
| boolean | boolean | bool (True / False) |
| null | null | None |
| object | object (plain) | dict |
| array | Array | list |
Python decides between int and float when parsing a number based on whether a decimal point or exponent is present, whereas JavaScript funnels every number into a single IEEE-754 double. Also mind the casing when writing JSON by hand in Python: JSON uses lowercase true/false/null, not Python's True/False/None — json.dumps handles the translation for you.
import json
data = {
"name": "Ada",
"age": 36,
"score": 9.5,
"active": True,
"nickname": None,
"roles": ["admin", "editor"],
}
text = json.dumps(data) # dict -> JSON string
restored = json.loads(text) # JSON string -> dict
print(type(restored["age"])) # <class 'int'>
print(type(restored["score"])) # <class 'float'>
print(restored["active"]) # True
print(restored["nickname"]) # NoneFor the full API on both sides, see Working with JSON in JavaScript and Working with JSON in Python.
Gotchas: Big Integers, Dates, and null vs Missing
Integer precision loss
JSON itself imposes no limit on the size of a number, but most parsers read numbers into a 64-bit IEEE-754 double, which represents integers exactly only up to 2^53 − 1 (9,007,199,254,740,991). Larger integers — 64-bit database IDs, snowflake IDs, some blockchain values — get silently rounded. This is a real, hard-to-spot data-corruption bug.
const raw = '{ "id": 9007199254740993 }';
const parsed = JSON.parse(raw);
console.log(parsed.id); // 9007199254740992 <-- wrong, rounded down
// The fix: keep the value as a string end to end
const safe = JSON.parse('{ "id": "9007199254740993" }');
console.log(safe.id); // "9007199254740993" <-- exactDates are just strings
Since JSON has no date type, standardize on ISO 8601 in UTC ("2026-07-12T14:30:00Z"). It sorts lexicographically in chronological order, is unambiguous about time zone, and every language can parse it. Avoid locale-specific formats like 07/12/2026, and note that JSON.stringify(new Date()) produces an ISO string but JSON.parse will not turn it back into a Date — you have to revive it yourself.
null vs a missing key
{ "nickname": null } and {} are not the same thing. The first explicitly states the nickname is known to be empty; the second says nothing about nickname at all. This distinction matters for PATCH-style APIs, where null often means "clear this field" while an absent key means "leave it unchanged." In JavaScript both reads yield a falsy value, so check with "nickname" in obj or Object.hasOwn(obj, "nickname") when the difference is meaningful. When diffing two payloads to see whether a key vanished or merely became null, a structural JSON Diff makes it obvious.
Validate and Inspect Your JSON
Types are easiest to reason about when the document is properly indented and confirmed well-formed. Format a raw payload to reveal its structure, then, if you need machine-enforced type rules across many documents, describe the expected shape with a schema.
{ }JSON FormatterOpen the tool→To lock down which types each field is allowed to hold — for example, requiring age to be an integer and email to be a string matching a pattern — define a schema. JSON Schema: A Complete Guide walks through it, and you can bootstrap one from a sample payload with the generator below.