JSON has six data types and no way to define your own. The type system is simple, but a few rules still catch experienced developers. There is no NaN, no trailing comma, no comment syntax and no date type. Large integers also lose precision past a certain size.
This guide covers all six JSON data types with examples. It shows how each maps to JavaScript and Python, and covers the bugs they cause in practice: lost precision on big integers, dates stored as strings, and the difference between null and a missing key. To see a payload's structure as you read, paste it into the JSON Formatter.
The six JSON data types at a glance
The JSON spec (ECMA-404 and RFC 8259) defines exactly six value types. Every valid JSON value is one of them. There is no seventh type and no way to register custom ones. Two are structural (object and array) and 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 is a single value. It is usually an object or an array, but a bare string or number is valid JSON too. Anything else (dates, sets, functions, undefined, big integers, binary data) has to be encoded with these six types, usually as a string. That limit causes most JSON surprises, so learn it 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 in double quotes. Single quotes are never allowed, which often trips up developers coming from Python or JavaScript. Some 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). You can also write any character as a \uXXXX Unicode escape.
Escaping by hand goes wrong easily when a string contains quotes or newlines. The JSON Escape / Unescape tool handles it in both directions, and How to Escape and Unescape JSON Strings covers the rules in detail.
Numbers
JSON numbers are base 10. One type covers both integers and floats, and the format has no separate int and float types. You can use a leading minus sign, a decimal point and scientific notation (1.5e-9). 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. They are never quoted or capitalized. null is its own type and means a value is deliberately empty. It is the only value of its type. A null value is not the same as a missing key, as covered below.
Objects and arrays: structure and nesting
Objects
An object is an unordered set of key/value pairs 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 lets JSON nest. The spec does not forbid duplicate keys, but it leaves their behavior undefined, and most parsers keep the last one. Treat duplicate keys as a bug.
Can JSON field names have spaces?
Yes. A key is any JSON string, so { "first name": "Ada" } is valid. Keys can also hold dots, dashes, emoji or be the empty string "". The catch is in the code that reads them. In JavaScript you need bracket notation (obj["first name"]) instead of dot notation. Code generators, JSONPath dot syntax and many databases also expect identifier-style keys. Use camelCase or snake_case unless you have a reason not to.
Arrays
An array is an ordered list of values in square brackets. JSON arrays can mix types, unlike arrays in statically typed languages. One array can hold strings, numbers, objects and other arrays. Order matters, and every conforming parser keeps it.
{
"users": [
{ "id": 1, "tags": ["a", "b"] },
{ "id": 2, "tags": [] }
],
"mixed": [1, "two", true, null, { "k": "v" }, [9, 8]]
}The spec sets no nesting limit. Parsers still cap depth to avoid stack overflows, and deep nesting is hard to read. When a payload gets hard to follow, the JSON Formatter indents it so you can see the nesting.
What JSON leaves out on purpose
Much of the confusion around JSON comes from what it does not have. Knowing these gaps up front saves debugging time:
- No date or time type. Dates are usually ISO 8601 strings (
"2026-07-12T14:30:00Z"). The parser sees a plain string and will not turn it into a date object for you. - No comments.
//and/* */are not valid JSON. For config files with comments, use JSON5, JSONC, or NDJSON instead. - No `undefined`. JavaScript's
undefinedis not a JSON value.JSON.stringifydrops object keys whose value isundefinedand turnsundefinedarray elements intonull. - No separate integer type, no BigInt. There is one
numbertype and no way to mark a value as an integer or a decimal. - No trailing commas, no single quotes, no hex or octal. The grammar is strict.
- No binary type. Binary data is usually Base64-encoded into a string.
The grammar enforces these rules, so a JSON Validator catches 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 almost one to one. Python's json module maps types just as cleanly, with a few different names.
| 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 picks int or float for a number based on whether it has a decimal point or exponent. JavaScript stores every number as one IEEE-754 double. Watch the casing when you write JSON by hand in Python. JSON uses lowercase true/false/null, not Python's True/False/None. json.dumps does 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 puts no limit on the size of a number. Most parsers, though, read numbers into a 64-bit IEEE-754 double, which holds integers exactly only up to 2^53 - 1 (9,007,199,254,740,991). Larger integers get rounded without warning. Examples include 64-bit database IDs, snowflake IDs and some blockchain values. This corrupts data and is hard to spot.
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
JSON has no date type, so standardize on ISO 8601 in UTC ("2026-07-12T14:30:00Z"). These strings sort in time order, state the time zone clearly, and every language can parse them. Avoid locale formats like 07/12/2026. Note that JSON.stringify(new Date()) outputs an ISO string, but JSON.parse will not turn it back into a Date. You have to convert it yourself.
null vs a missing key
{ "nickname": null } and {} are different. The first says the nickname is known to be empty. The second says nothing about nickname at all. This matters for PATCH-style APIs, where null often means "clear this field" and a missing key means "leave it unchanged". In JavaScript both reads give a falsy value, so check with "nickname" in obj or Object.hasOwn(obj, "nickname") when the difference matters. To see whether a key disappeared or just became null between two payloads, use JSON Diff.
Validate and inspect your JSON
Types are easier to reason about when the document is indented and known to be valid. Format a raw payload to see its structure. If you need type rules enforced across many documents, describe the expected shape with a schema.
A schema sets which types each field may hold. For example, it can require age to be an integer and email to be a string that matches a pattern. JSON Schema: A Complete Guide explains how, and the generator below builds a starting schema from a sample payload.