Docs
README
11.2 Working with JSON
š Table of Contents
- ā¢JSON Overview
- ā¢JSON.stringify()
- ā¢JSON.parse()
- ā¢Handling Special Types
- ā¢Deep Cloning
- ā¢JSON Schema Validation
- ā¢Best Practices
JSON Overview
JSON (JavaScript Object Notation) is a lightweight data interchange format.
JSON vs JavaScript Objects
āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā
ā JavaScript Object vs JSON ā
āāāāāāāāāāāāāāāāāāāāāāāāāāāāāā¬āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā¤
ā JavaScript Object ā JSON String ā
āāāāāāāāāāāāāāāāāāāāāāāāāāāāāā¼āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā¤
ā { ā '{"name":"John","age":30}' ā
ā name: "John", ā ā
ā age: 30 ā - Keys MUST be double-quoted ā
ā } ā - No trailing commas ā
ā ā - No undefined, functions, symbols ā
ā - Can have any key style ā - No comments ā
ā - Supports all JS types ā - No NaN, Infinity ā
ā - Lives in memory ā - Can be stored/transmitted ā
āāāāāāāāāāāāāāāāāāāāāāāāāāāāāā“āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā
Valid JSON Types
āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā
ā Valid JSON Values ā
āāāāāāāāāāāāāāāāāāā¬āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā¤
ā Type ā Example ā
āāāāāāāāāāāāāāāāāāā¼āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā¤
ā String ā "Hello World" ā
ā Number ā 42, 3.14, -10, 1e5 ā
ā Boolean ā true, false ā
ā Null ā null ā
ā Array ā [1, 2, 3], ["a", "b"] ā
ā Object ā {"key": "value"} ā
āāāāāāāāāāāāāāāāāāā¼āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā¤
ā NOT VALID ā undefined, NaN, Infinity, -Infinity ā
ā ā Functions, Symbols, BigInt ā
ā ā Dates (become strings) ā
ā ā Map, Set, WeakMap, WeakSet ā
āāāāāāāāāāāāāāāāāāā“āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā
JSON.stringify()
Converts JavaScript values to JSON strings.
Basic Usage
const data = { name: 'John', age: 30 };
const json = JSON.stringify(data);
console.log(json); // '{"name":"John","age":30}'
Syntax
JSON.stringify(value, replacer, space);
| Parameter | Description |
|---|---|
| value | The value to convert |
| replacer | Function or array to filter properties |
| space | Number or string for indentation |
Pretty Printing
const user = {
name: 'John',
email: 'john@example.com',
address: {
city: 'New York',
zip: '10001',
},
};
// Compact (default)
console.log(JSON.stringify(user));
// {"name":"John","email":"john@example.com","address":{"city":"New York","zip":"10001"}}
// Pretty with 2 spaces
console.log(JSON.stringify(user, null, 2));
/*
{
"name": "John",
"email": "john@example.com",
"address": {
"city": "New York",
"zip": "10001"
}
}
*/
// Pretty with tab
console.log(JSON.stringify(user, null, '\t'));
Replacer Function
const user = {
name: 'John',
password: 'secret123',
email: 'john@example.com',
age: 30,
};
// Filter out sensitive data
const json = JSON.stringify(user, (key, value) => {
if (key === 'password') return undefined; // Exclude
if (typeof value === 'string') return value.toUpperCase(); // Transform
return value;
});
console.log(json);
// {"name":"JOHN","email":"JOHN@EXAMPLE.COM","age":30}
Replacer Flow
āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā
ā Replacer Function Flow ā
āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā
JSON.stringify(obj, replacer)
āāāāāāāāāāāāāāāāāāāā
ā '' (empty key) ā āāā Called first with whole object
ā value = obj ā
āāāāāāāāāā¬āāāāāāāāāā
ā
ā¼
āāāāāāāāāāāāāāāāāāāā
ā key = 'name' ā
ā value = 'John' ā
āāāāāāāāāā¬āāāāāāāāāā
ā
ā¼
āāāāāāāāāāāāāāāāāāāā
ā key = 'age' ā
ā value = 30 ā
āāāāāāāāāā¬āāāāāāāāāā
ā
ā¼
... continues
Replacer returns:
- undefined ā Property excluded
- any value ā Used in output
Replacer Array
const user = {
id: 1,
name: 'John',
email: 'john@example.com',
password: 'secret',
role: 'admin',
};
// Only include specified properties
const json = JSON.stringify(user, ['id', 'name', 'email']);
console.log(json);
// {"id":1,"name":"John","email":"john@example.com"}
toJSON Method
Objects can define custom JSON representation:
const meeting = {
title: 'Team Sync',
date: new Date('2024-12-15'),
attendees: ['Alice', 'Bob'],
toJSON() {
return {
title: this.title,
date: this.date.toISOString(),
attendeeCount: this.attendees.length,
};
},
};
console.log(JSON.stringify(meeting, null, 2));
/*
{
"title": "Team Sync",
"date": "2024-12-15T00:00:00.000Z",
"attendeeCount": 2
}
*/
JSON.parse()
Converts JSON strings to JavaScript values.
Basic Usage
const json = '{"name":"John","age":30}';
const data = JSON.parse(json);
console.log(data.name); // "John"
console.log(data.age); // 30
Syntax
JSON.parse(text, reviver);
| Parameter | Description |
|---|---|
| text | The JSON string to parse |
| reviver | Function to transform values |
Reviver Function
const json = '{"name":"John","birthDate":"1990-05-15T00:00:00.000Z"}';
const user = JSON.parse(json, (key, value) => {
// Convert ISO date strings to Date objects
if (key === 'birthDate') {
return new Date(value);
}
return value;
});
console.log(user.birthDate instanceof Date); // true
console.log(user.birthDate.getFullYear()); // 1990
Reviver Flow
āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā
ā Reviver Function Flow ā
āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā
JSON.parse(json, reviver)
(Processes bottom-up, innermost first)
āāāāāāāāāāāāāāāāāāāā
ā key = 'name' ā
ā value = 'John' ā
āāāāāāāāāā¬āāāāāāāāāā
ā
ā¼
āāāāāāāāāāāāāāāāāāāā
ā key = 'birthDate'ā
ā value = '1990...'ā āāŗ return new Date(value)
āāāāāāāāāā¬āāāāāāāāāā
ā
ā¼
āāāāāāāāāāāāāāāāāāāā
ā '' (empty key) ā āāā Called last with whole object
ā value = {...} ā
āāāāāāāāāāāāāāāāāāāā
Error Handling
function safeJsonParse(json, defaultValue = null) {
try {
return JSON.parse(json);
} catch (error) {
console.error('JSON parse error:', error.message);
return defaultValue;
}
}
// Usage
console.log(safeJsonParse('{"valid": true}')); // { valid: true }
console.log(safeJsonParse('invalid json')); // null
console.log(safeJsonParse('broken', { error: true })); // { error: true }
Common Parse Errors
āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā
ā Common JSON Parse Errors ā
āāāāāāāāāāāāāāāāāāāāāāāāāāāāāā¬āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā¤
ā JSON String ā Problem ā
āāāāāāāāāāāāāāāāāāāāāāāāāāāāāā¼āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā¤
ā {name: "John"} ā Keys must be quoted ā
ā {'name': 'John'} ā Must use double quotes ā
ā {"name": "John",} ā Trailing comma not allowed ā
ā {"name": undefined} ā undefined is not valid JSON ā
ā {"name": NaN} ā NaN is not valid JSON ā
ā // comment {"a": 1} ā Comments not allowed ā
āāāāāāāāāāāāāāāāāāāāāāāāāāāāāā“āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā
Handling Special Types
Dates
// Problem: Dates become strings
const event = {
name: 'Meeting',
date: new Date('2024-12-15'),
};
const json = JSON.stringify(event);
console.log(json);
// {"name":"Meeting","date":"2024-12-15T00:00:00.000Z"}
const parsed = JSON.parse(json);
console.log(parsed.date); // String, not Date!
console.log(typeof parsed.date); // "string"
// Solution: Use reviver
function dateReviver(key, value) {
const isoDateRegex = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}/;
if (typeof value === 'string' && isoDateRegex.test(value)) {
return new Date(value);
}
return value;
}
const restored = JSON.parse(json, dateReviver);
console.log(restored.date instanceof Date); // true
Maps and Sets
// Maps and Sets don't stringify properly
const data = {
myMap: new Map([
['a', 1],
['b', 2],
]),
mySet: new Set([1, 2, 3]),
};
console.log(JSON.stringify(data));
// {"myMap":{},"mySet":{}} - Data lost!
// Solution: Custom serialization
function serializeWithCollections(data) {
return JSON.stringify(data, (key, value) => {
if (value instanceof Map) {
return { __type: 'Map', data: [...value] };
}
if (value instanceof Set) {
return { __type: 'Set', data: [...value] };
}
return value;
});
}
function parseWithCollections(json) {
return JSON.parse(json, (key, value) => {
if (value && value.__type === 'Map') {
return new Map(value.data);
}
if (value && value.__type === 'Set') {
return new Set(value.data);
}
return value;
});
}
// Usage
const serialized = serializeWithCollections(data);
console.log(serialized);
// {"myMap":{"__type":"Map","data":[["a",1],["b",2]]},"mySet":{"__type":"Set","data":[1,2,3]}}
const restored = parseWithCollections(serialized);
console.log(restored.myMap.get('a')); // 1
console.log(restored.mySet.has(2)); // true
Functions and Symbols (Not Supported)
const obj = {
name: 'John',
greet: function () {
return 'Hello';
}, // Will be ignored
[Symbol('id')]: 123, // Will be ignored
};
console.log(JSON.stringify(obj));
// {"name":"John"} - Function and Symbol lost!
Circular References
const obj = { name: 'John' };
obj.self = obj; // Circular reference
// This throws an error!
try {
JSON.stringify(obj);
} catch (e) {
console.log(e.message); // "Converting circular structure to JSON"
}
// Solution: Use a replacer that handles circular refs
function stringifyWithCircular(obj) {
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;
});
}
console.log(stringifyWithCircular(obj));
// {"name":"John","self":"[Circular]"}
Deep Cloning
Using JSON (Simple Cases)
const original = {
name: 'John',
address: {
city: 'NYC',
zip: '10001',
},
hobbies: ['reading', 'coding'],
};
// Deep clone using JSON
const clone = JSON.parse(JSON.stringify(original));
// Verify it's a deep clone
clone.address.city = 'LA';
clone.hobbies.push('gaming');
console.log(original.address.city); // 'NYC' (unchanged)
console.log(original.hobbies); // ['reading', 'coding'] (unchanged)
Limitations of JSON Cloning
āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā
ā JSON.parse(JSON.stringify()) Limitations ā
āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā¤
ā ā
ā ā Loses Date objects (become strings) ā
ā ā Loses undefined values ā
ā ā Loses functions ā
ā ā Loses Symbol keys ā
ā ā Loses Map/Set (become empty objects) ā
ā ā Loses prototype chain ā
ā ā Cannot handle circular references ā
ā ā Loses RegExp (becomes empty object) ā
ā ā NaN and Infinity become null ā
ā ā
ā ā Works for plain objects with JSON-safe values ā
ā ā Works for arrays with JSON-safe values ā
ā ā Works for primitives (strings, numbers, booleans, null) ā
ā ā
āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā
structuredClone (Modern Alternative)
// structuredClone handles more types
const original = {
name: 'John',
date: new Date(),
regex: /pattern/gi,
data: new Map([['key', 'value']]),
};
const clone = structuredClone(original);
console.log(clone.date instanceof Date); // true
console.log(clone.regex instanceof RegExp); // true
console.log(clone.data instanceof Map); // true
// Still can't clone:
// - Functions
// - DOM nodes
// - Symbols (as keys or values)
Custom Deep Clone
function deepClone(obj, seen = new WeakMap()) {
// Handle primitives and null
if (obj === null || typeof obj !== 'object') {
return obj;
}
// Handle circular references
if (seen.has(obj)) {
return seen.get(obj);
}
// Handle Date
if (obj instanceof Date) {
return new Date(obj.getTime());
}
// Handle RegExp
if (obj instanceof RegExp) {
return new RegExp(obj.source, obj.flags);
}
// Handle Map
if (obj instanceof Map) {
const clone = new Map();
seen.set(obj, clone);
obj.forEach((value, key) => {
clone.set(deepClone(key, seen), deepClone(value, seen));
});
return clone;
}
// Handle Set
if (obj instanceof Set) {
const clone = new Set();
seen.set(obj, clone);
obj.forEach((value) => {
clone.add(deepClone(value, seen));
});
return clone;
}
// Handle Array
if (Array.isArray(obj)) {
const clone = [];
seen.set(obj, clone);
obj.forEach((item, index) => {
clone[index] = deepClone(item, seen);
});
return clone;
}
// Handle Object
const clone = Object.create(Object.getPrototypeOf(obj));
seen.set(obj, clone);
for (const key of Reflect.ownKeys(obj)) {
clone[key] = deepClone(obj[key], seen);
}
return clone;
}
JSON Schema Validation
Simple Validation
function validateJSON(data, schema) {
for (const [key, rules] of Object.entries(schema)) {
const value = data[key];
// Required check
if (rules.required && (value === undefined || value === null)) {
return { valid: false, error: `${key} is required` };
}
// Type check
if (value !== undefined && rules.type) {
const actualType = Array.isArray(value) ? 'array' : typeof value;
if (actualType !== rules.type) {
return { valid: false, error: `${key} must be ${rules.type}` };
}
}
// Min/Max for numbers
if (typeof value === 'number') {
if (rules.min !== undefined && value < rules.min) {
return { valid: false, error: `${key} must be >= ${rules.min}` };
}
if (rules.max !== undefined && value > rules.max) {
return { valid: false, error: `${key} must be <= ${rules.max}` };
}
}
// MinLength/MaxLength for strings
if (typeof value === 'string') {
if (rules.minLength && value.length < rules.minLength) {
return {
valid: false,
error: `${key} must be at least ${rules.minLength} chars`,
};
}
if (rules.maxLength && value.length > rules.maxLength) {
return {
valid: false,
error: `${key} must be at most ${rules.maxLength} chars`,
};
}
}
}
return { valid: true };
}
// Usage
const userSchema = {
name: { required: true, type: 'string', minLength: 2 },
age: { required: true, type: 'number', min: 0, max: 150 },
email: { required: true, type: 'string' },
};
const user = { name: 'J', age: 30, email: 'j@example.com' };
console.log(validateJSON(user, userSchema));
// { valid: false, error: "name must be at least 2 chars" }
Best Practices
1. Always Handle Parse Errors
function safeParse(json, fallback = null) {
try {
return JSON.parse(json);
} catch {
return fallback;
}
}
2. Validate Before Parsing External Data
function parseAPIResponse(response) {
// Check if it's a string
if (typeof response !== 'string') {
throw new Error('Response must be a string');
}
// Try to parse
const data = JSON.parse(response);
// Validate structure
if (!data || typeof data !== 'object') {
throw new Error('Invalid response structure');
}
return data;
}
3. Use Pretty Print for Debugging
// Log objects nicely
function prettyLog(label, data) {
console.log(`${label}:\n${JSON.stringify(data, null, 2)}`);
}
prettyLog('User Data', { name: 'John', scores: [100, 95, 88] });
4. Be Careful with Sensitive Data
// Create a safe stringify function
function safeStringify(obj, sensitiveKeys = ['password', 'token', 'secret']) {
return JSON.stringify(obj, (key, value) => {
if (sensitiveKeys.includes(key)) {
return '[REDACTED]';
}
return value;
});
}
const user = { name: 'John', password: 'secret123' };
console.log(safeStringify(user));
// {"name":"John","password":"[REDACTED]"}
5. Consider Size for Large Objects
function getJSONSize(obj) {
const json = JSON.stringify(obj);
return {
characters: json.length,
bytes: new Blob([json]).size,
kilobytes: (new Blob([json]).size / 1024).toFixed(2),
};
}
console.log(getJSONSize({ data: 'x'.repeat(1000) }));
// { characters: 1015, bytes: 1015, kilobytes: '0.99' }
Quick Reference
// Basic operations
JSON.stringify(obj) // Object to JSON string
JSON.parse(json) // JSON string to object
// Pretty print
JSON.stringify(obj, null, 2) // 2-space indent
JSON.stringify(obj, null, '\t') // Tab indent
// Filter properties
JSON.stringify(obj, ['a', 'b']) // Only include a, b
JSON.stringify(obj, (k, v) => ...) // Custom replacer
// Transform on parse
JSON.parse(json, (k, v) => ...) // Custom reviver
// Deep clone (simple)
JSON.parse(JSON.stringify(obj))
// Deep clone (modern)
structuredClone(obj)
Summary
| Concept | Key Points |
|---|---|
| stringify | Converts JS to JSON string, loses functions/undefined/symbols |
| parse | Converts JSON string to JS, throws on invalid JSON |
| replacer | Filter/transform values during stringify |
| reviver | Transform values during parse (bottom-up) |
| toJSON | Custom stringify behavior for objects |
| Dates | Become ISO strings, need manual revival |
| Circular refs | Throw error, need custom handling |
| Deep clone | JSON method loses types, use structuredClone |