What Is JSON? A Developer's Guide to JavaScript Object Notation
Common misconception: "JSON is a JavaScript thing — you need JavaScript to use it."
Wrong. JSON is defined in RFC 8259 (an Internet Standard, not a JavaScript specification) and is explicitly language-independent. It is parsed natively in Python, Go, Rust, Java, C++, Ruby, PHP, and dozens more. The "JavaScript" in the name is historical — its syntax was inspired by JS object literals, but the spec has been independent since RFC 4627 in 2006.
Key Takeaways
- ▸JSON has exactly six value types: string, number, boolean, null, object, array. No dates, no undefined, no binary.
- ▸RFC 8259 (2017) is the current Internet Standard. It requires UTF-8 encoding and doubles as ISO/IEC 21778:2017.
- ▸JSON.parse() and JSON.stringify() are synchronous and block the event loop — avoid them with payloads above ~50MB in Node.js.
- ▸JSON cannot contain comments by design. For config files that need comments, use JSONC or YAML.
- ▸For high-throughput APIs, MessagePack can be 2–10× smaller than JSON. For typed RPC, Protocol Buffers offer better performance and schema enforcement.
A Brief History: From JavaScript Hack to Internet Standard
In 2001, Douglas Crockford was working at State Software and needed a way to transmit data between a web application and a server without browser plugins. He noticed that JavaScript object literal syntax — already present in ECMA-262 3rd Edition (1999) — was a valid, minimal data format. The first JSON message was sent in 2001.
The standardization timeline followed in three stages:
| Year | Standard | Significance |
|---|---|---|
| 2001 | json.org | First public specification by Douglas Crockford |
| 2006 | RFC 4627 | First IETF spec (Informational, not a standard) |
| 2013 | ECMA-404 | First formal standard; minimal, focused on syntax only |
| 2013 | RFC 7159 | Replaced RFC 4627; clarified encoding requirements |
| 2017 | RFC 8259 / STD 90 | Current Internet Standard. Mandates UTF-8 for network exchange |
| 2017 | ISO/IEC 21778:2017 | JSON as an ISO standard, consistent with RFC 8259 |
Tim Bray, who edited RFC 8259, noted that it was likely the final JSON spec — the format was complete. The goal was to clean up inconsistencies between RFC 7159 and ECMA-404, not to add features. Per RFC 8259: "A JSON text is a serialized value."
JSON Syntax: The Complete Grammar
JSON's grammar is famously small. The json.org railroad diagrams fit on a single page. The entire format has six value types, four structural characters, and three literal names.
{
"string": "Must use double quotes — not single quotes",
"number_integer": 42,
"number_float": 3.14159,
"number_scientific": 1.5e10,
"boolean_true": true,
"boolean_false": false,
"null_value": null,
"nested_object": {
"key": "value",
"another_key": 123
},
"array": [1, "two", true, null, {"nested": "in array"}],
"empty_object": {},
"empty_array": []
}
# What JSON does NOT support:
# - Single-quoted strings: 'bad' ← SyntaxError
# - Unquoted keys: {key: "value"} ← SyntaxError
# - Trailing commas: [1, 2, 3,] ← SyntaxError
# - Comments: // or /* */ ← SyntaxError
# - undefined: undefined ← not a JSON value
# - NaN or Infinity: NaN, Infinity ← not valid JSON numbers
# - Date objects: new Date() ← not a JSON type
# - Functions: () => {} ← not a JSON type
# - Hexadecimal numbers: 0xFF ← not valid
# - Binary/octal: 0b1010, 0o17 ← not validString Encoding Rules
RFC 8259 mandates UTF-8 encoding for JSON transmitted over networks. JSON strings support Unicode escape sequences (\uXXXX) and a fixed set of backslash escapes:
{
"escape_sequences": {
"quote": "He said "hello"", // " — escaped double quote
"backslash": "C:\Users\name", // \ — escaped backslash
"slash": "https://example.com", // / — optional (/ works too)
"newline": "line1
line2", //
"tab": "col1 col2", //
"carriage": "text
", //
"formfeed": "page", //
"backspace": "textmore", //
"unicode": "élève", // é, è (UTF-8: valid)
"emoji": "🚀" // 🚀 (surrogate pair in JSON)
}
}
# Gotcha: JSON numbers have no size limit in the spec,
# but JavaScript's Number type loses precision above 2^53-1.
# Use strings for large integers (Twitter IDs, database IDs):
{
"tweet_id": "1234567890123456789", // string — safe
"tweet_id_bad": 1234567890123456789 // number — precision lost in JS!
}Parsing JSON: Language by Language
JSON parsing is built into the standard library of every major language. Here's production-quality parsing code with proper error handling:
JavaScript / TypeScript
// Basic parse/stringify
const json = '{"name":"Alice","age":30,"scores":[95,87,92]}';
const obj = JSON.parse(json);
console.log(obj.name); // "Alice"
const back = JSON.stringify(obj, null, 2); // 3rd arg = indent spaces
// Error handling (JSON.parse throws SyntaxError on bad input)
function safeParse<T>(text: string): T | null {
try {
return JSON.parse(text) as T;
} catch {
return null;
}
}
// Reviver: transform values during parsing
const withDates = JSON.parse('{"created":"2026-01-15T10:30:00Z"}',
(key, value) => {
if (typeof value === 'string' && /^d{4}-d{2}-d{2}T/.test(value)) {
return new Date(value); // Convert ISO strings to Date objects
}
return value;
}
);
console.log(withDates.created instanceof Date); // true
// Replacer: control what gets serialized
const data = { name: "Bob", password: "secret", age: 25 };
const safe = JSON.stringify(data, (key, value) => {
if (key === "password") return undefined; // Omit sensitive fields
return value;
});
// '{"name":"Bob","age":25}'
// ES2025: Import JSON directly in modules (Stage 4)
// import config from './config.json' with { type: 'json' };Python
import json
from datetime import datetime
# Parse (str → dict)
text = '{"name": "Alice", "scores": [95, 87, 92]}'
data = json.loads(text)
print(data["name"]) # Alice
# Serialize (dict → str)
output = json.dumps(data, indent=2, sort_keys=True)
# File I/O
with open("data.json", "w") as f:
json.dump(data, f, indent=2)
with open("data.json") as f:
loaded = json.load(f)
# Custom encoder for non-serializable types
class DateEncoder(json.JSONEncoder):
def default(self, obj):
if isinstance(obj, datetime):
return obj.isoformat()
return super().default(obj)
result = json.dumps({"ts": datetime.now()}, cls=DateEncoder)
# '{"ts": "2026-04-12T10:30:00.123456"}'
# Custom decoder with object_hook
def decode_dates(d: dict) -> dict:
for key, value in d.items():
if isinstance(value, str) and value.endswith("Z"):
try:
d[key] = datetime.fromisoformat(value.rstrip("Z"))
except ValueError:
pass
return d
parsed = json.loads('{"created": "2026-01-15T10:30:00Z"}',
object_hook=decode_dates)Go
package main
import (
"encoding/json"
"fmt"
"time"
)
type User struct {
Name string `json:"name"`
Age int `json:"age"`
CreatedAt time.Time `json:"created_at"`
Password string `json:"-"` // Always omit
Bio string `json:"bio,omitempty"` // Omit if empty
}
func main() {
// Unmarshal (JSON string → struct)
data := `{"name":"Alice","age":30,"created_at":"2026-01-15T10:30:00Z"}`
var user User
if err := json.Unmarshal([]byte(data), &user); err != nil {
panic(err)
}
fmt.Println(user.Name) // Alice
// Marshal (struct → JSON)
user.Name = "Bob"
output, err := json.MarshalIndent(user, "", " ")
if err != nil {
panic(err)
}
fmt.Println(string(output))
// Dynamic JSON with map (when schema is unknown)
var dynamic map[string]interface{}
json.Unmarshal([]byte(data), &dynamic)
fmt.Println(dynamic["name"]) // Alice (type: interface{})
// Streaming large JSON files (memory efficient)
// dec := json.NewDecoder(file)
// for dec.More() { dec.Decode(&item) }
}JSON Performance: What the Benchmarks Say
JSON's text-based nature has a real performance cost compared to binary formats. Here's an honest comparison based on published benchmarks from the serialization benchmarks repo and language-specific profiling:
| Format | Relative Size | Parse Speed | Human Readable | Schema |
|---|---|---|---|---|
| JSON | 1× (baseline) | 1× (baseline) | Yes | Optional (JSON Schema) |
| MessagePack | 0.4–0.6× smaller | 2–5× faster | No (binary) | No |
| Protocol Buffers | 0.3–0.5× smaller | 3–10× faster | No (binary) | Required (.proto) |
| CBOR | ~0.6× smaller | 2–4× faster | No (binary) | Optional |
| XML | 1.5–3× larger | 0.3–0.5× slower | Yes | Optional (XSD) |
| YAML | ~1.2× larger | 5–10× slower | Yes | No |
The takeaway: JSON wins on tooling, readability, and universal support. For internal microservice communication at high throughput, MessagePack or Protobuf can cut bandwidth and latency significantly. For human-edited config files, YAML vs JSON is a reasonable comparison — YAML's comments and multiline strings often win there.
Node.js JSON Performance Pitfalls
// Pitfall 1: Synchronous parse blocks the event loop
// For small payloads (< 1MB): fine
const data = JSON.parse(largeString); // Blocks for 10-50ms at 50MB
// For large payloads: stream instead
import { pipeline } from 'stream/promises';
import { createReadStream } from 'fs';
import JSONStream from 'JSONStream'; // npm install JSONStream
const stream = createReadStream('large-data.json')
.pipe(JSONStream.parse('items.*')); // Stream array items
stream.on('data', (item) => {
// Process one item at a time — no memory spike
});
// Pitfall 2: JSON.stringify with circular references throws
const a = { name: 'a' };
a.self = a; // Circular!
JSON.stringify(a); // TypeError: Converting circular structure to JSON
// Fix: use a WeakSet to detect cycles
function safeStringify(obj: unknown): string {
const seen = new WeakSet();
return JSON.stringify(obj, (key, value) => {
if (typeof value === 'object' && value !== null) {
if (seen.has(value)) return '[Circular]';
seen.add(value);
}
return value;
});
}
// Pitfall 3: Numbers > Number.MAX_SAFE_INTEGER
const tweet = JSON.parse('{"id": 1876543210987654321}');
console.log(tweet.id); // 1876543210987654400 — precision lost!
// Fix: use BigInt JSON parsers (e.g., json-bigint)
import JSONbig from 'json-bigint';
const parsed = JSONbig.parse('{"id": 1876543210987654321}');
console.log(parsed.id.toString()); // "1876543210987654321" — exactJSON in the Wild: Real-World Patterns
REST API Responses
JSON is the de facto format for REST APIs. According to the 2025 Stack Overflow Developer Survey of over 49,000 developers, REST remains the dominant API paradigm. The JSON-based nature of REST is a major reason for its adoption — every HTTP client in every language can parse it without a library.
// Standard JSON API response envelope
{
"data": {
"id": "usr_abc123",
"type": "user",
"attributes": {
"name": "Alice Smith",
"email": "[email protected]",
"created_at": "2026-01-15T10:30:00Z" // ISO 8601 — always UTC
}
},
"meta": {
"request_id": "req_xyz789",
"version": "2026-04-01"
}
}
// Paginated list response
{
"data": [...],
"pagination": {
"total": 1847,
"page": 1,
"per_page": 20,
"next_cursor": "eyJpZCI6MTAwfQ==" // Cursor-based pagination
}
}
// Error response (follow RFC 7807 Problem Details)
{
"type": "https://api.example.com/errors/validation",
"title": "Validation Failed",
"status": 422,
"detail": "The 'email' field must be a valid email address.",
"instance": "/users/register",
"errors": [
{ "field": "email", "message": "Invalid email format", "code": "INVALID_EMAIL" }
]
}Configuration Files
JSON powers the JavaScript ecosystem's configuration: package.json, tsconfig.json, .eslintrc.json, .prettierrc. There are over 5 million public package.json files on GitHub — it's likely the most-read JSON document format in existence.
// package.json — the backbone of npm's 3M+ packages
{
"name": "my-app",
"version": "1.0.0",
"scripts": {
"dev": "next dev",
"build": "next build",
"test": "jest"
},
"dependencies": {
"next": "^15.0.0",
"react": "^19.0.0"
},
"devDependencies": {
"typescript": "^5.6.0",
"@types/react": "^19.0.0"
},
"engines": {
"node": ">=20.0.0"
}
}
// tsconfig.json — TypeScript uses JSONC (allows comments)
// Note: standard JSON.parse() would REJECT this file!
{
"compilerOptions": {
"target": "ES2022", // This comment is valid in JSONC
"strict": true,
"paths": {
"@/*": ["./src/*"]
}
}
}JSON Schema Validation
JSON Schema (json-schema.org) allows you to define the shape of JSON data and validate documents against it. It's used for API contract testing, form validation, and configuration validation. Read the full guide on JSON Schema validation for production patterns.
// JSON Schema for a User object
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "https://api.example.com/schemas/user.json",
"type": "object",
"required": ["name", "email"],
"properties": {
"name": {
"type": "string",
"minLength": 1,
"maxLength": 100
},
"email": {
"type": "string",
"format": "email"
},
"age": {
"type": "integer",
"minimum": 0,
"maximum": 150
},
"roles": {
"type": "array",
"items": { "enum": ["admin", "editor", "viewer"] },
"uniqueItems": true
}
},
"additionalProperties": false // Reject unknown fields
}
// Validate in Node.js with Ajv (the most popular JSON Schema validator)
import Ajv from 'ajv';
const ajv = new Ajv({ allErrors: true });
const validate = ajv.compile(userSchema);
const valid = validate({ name: "Alice", email: "bad-email" });
// false — errors: [{ message: 'must match format "email"' }]JSON vs Alternatives: When to Switch
JSON is the right default for most use cases. Here's when to reconsider — and what to use instead:
- Use YAML instead of JSON when: you need human-edited config files with comments, multiline strings, or anchors/aliases for DRY configuration (Kubernetes manifests, GitHub Actions, docker-compose). Note: YAML parsers are 5–10× slower than JSON parsers.
- Use MessagePack instead of JSON when: you control both client and server, throughput matters more than debuggability, and you're transmitting large volumes of numeric or binary data. Used by Redis, Avro, and many gaming backends.
- Use Protocol Buffers instead of JSON when: you need strict schema enforcement, generated client libraries in multiple languages, and maximum parse performance. Google uses Protobuf internally for virtually all inter-service communication. gRPC defaults to Protobuf.
- Use NDJSON (Newline-Delimited JSON) instead of JSON when: streaming large datasets — each line is a valid JSON document. Used by Docker logs, Elasticsearch bulk API, and log aggregation pipelines. Parseable line-by-line without loading the full file into memory.
For a deeper comparison between JSON and XML specifically, see our JSON vs XML breakdown — including when XML's namespace support and XPath querying remain genuinely useful.
Frequently Asked Questions
What is JSON and what is it used for?
JSON (JavaScript Object Notation) is a lightweight text-based data interchange format defined in RFC 8259. Used for REST API responses, configuration files (package.json, tsconfig.json), data exchange between services, and document databases like MongoDB. Language-independent — parsed natively in Python, Go, Java, Rust, and dozens more.
What are the 6 JSON data types?
String (double-quoted text), number (integer or float — no NaN or Infinity), boolean (true or false), null, object (unordered key-value pairs with string keys), and array (ordered list). No dates, no undefined, no binary data, no functions.
Is JSON the same as a JavaScript object?
No. JSON requires double quotes for all keys and strings. JavaScript allows single quotes and unquoted keys. JSON has no trailing commas, comments, undefined, or functions. A JavaScript object is a live runtime value; JSON is always a string representation.
What is the difference between JSON.parse() and JSON.stringify()?
JSON.parse() converts a JSON string to a JavaScript object. JSON.stringify() converts a JavaScript object to a JSON string. parse() throws SyntaxError on bad input. stringify() silently drops undefined, functions, and Symbols. Both accept a second function argument for transformation.
Why can't JSON have comments?
Douglas Crockford intentionally excluded comments to prevent them being used as parsing directives. For config files needing comments, use JSONC (VS Code, TypeScript's tsconfig.json) or YAML.
What is JSON5 and should I use it?
JSON5 is a superset adding comments, trailing commas, single-quoted strings, and unquoted keys. Useful for human-edited configs (Babel uses it). For APIs, stick to RFC 8259 JSON — universal parser support and faster parsing.
When is JSON a bad choice?
Binary data (33% Base64 overhead), large numeric datasets, high-throughput microservice RPC (use MessagePack or Protobuf), and strongly-typed systems where schema enforcement and code generation matter more than flexibility.
Validate and Format JSON Instantly
Paste any JSON into our formatter to validate syntax, pretty-print with configurable indentation, and explore nested structure. Includes error highlighting with exact line/column numbers — no "unexpected token" mystery messages. Need to convert formats? Try the JSON to CSV converter or JSON to YAML converter.
Open JSON Formatter