JavaScript FundamentalsFeatured
JavaScript JSON: Data Interchange Format Guide
Master JSON in JavaScript. Learn parsing, stringifying, data manipulation, API integration, and best practices for working with JSON.
By JavaScriptDoc Team•
jsonjavascriptdataapiparsing
JavaScript JSON: Data Interchange Format Guide
JSON (JavaScript Object Notation) is a lightweight data interchange format that's easy for humans to read and write, and easy for machines to parse and generate.
Understanding JSON
JSON is a text format that's completely language-independent but uses conventions familiar to JavaScript programmers.
// JSON data types
const jsonExamples = {
// Strings (must use double quotes)
string: 'Hello, World!',
// Numbers (integer or floating point)
integer: 42,
float: 3.14159,
// Booleans
boolean: true,
// Null
nullValue: null,
// Arrays
array: [1, 2, 3, 'four', true],
// Objects
object: {
name: 'John',
age: 30,
city: 'New York',
},
};
// What JSON cannot contain:
// - Functions
// - undefined
// - Symbols
// - Dates (converted to strings)
// - Comments
// - Trailing commas
JSON.parse()
Basic Parsing
// Parse JSON string to JavaScript object
const jsonString = '{"name": "John", "age": 30, "city": "New York"}';
const obj = JSON.parse(jsonString);
console.log(obj.name); // "John"
// Parse array
const arrayJson = '[1, 2, 3, "four", true]';
const arr = JSON.parse(arrayJson);
console.log(arr[3]); // "four"
// Parse nested structures
const nestedJson = `{
"user": {
"id": 1,
"name": "John Doe",
"contacts": {
"email": "john@example.com",
"phone": "+1234567890"
},
"hobbies": ["reading", "gaming", "coding"]
}
}`;
const nested = JSON.parse(nestedJson);
console.log(nested.user.contacts.email); // "john@example.com"
Parse with Reviver Function
// Reviver function to transform values during parsing
const dateJson = '{"date": "2024-03-15T10:30:00.000Z", "event": "meeting"}';
const parsed = JSON.parse(dateJson, (key, value) => {
// Convert date strings to Date objects
if (typeof value === 'string' && /\d{4}-\d{2}-\d{2}T/.test(value)) {
return new Date(value);
}
return value;
});
console.log(parsed.date instanceof Date); // true
// Complex reviver example
const complexJson = `{
"price": "19.99",
"quantity": "5",
"isActive": "true",
"metadata": "_REMOVE_THIS_"
}`;
const complexParsed = JSON.parse(complexJson, (key, value) => {
// Convert string numbers to numbers
if (typeof value === 'string' && !isNaN(value)) {
return parseFloat(value);
}
// Convert string booleans to booleans
if (value === 'true') return true;
if (value === 'false') return false;
// Remove specific values
if (value === '_REMOVE_THIS_') return undefined;
return value;
});
console.log(complexParsed);
// { price: 19.99, quantity: 5, isActive: true }
Error Handling
// Safe JSON parsing
function safeParse(jsonString, defaultValue = null) {
try {
return JSON.parse(jsonString);
} catch (error) {
console.error('JSON Parse Error:', error.message);
return defaultValue;
}
}
// Test with invalid JSON
const invalid1 = safeParse('{invalid json}', {}); // Returns {}
const invalid2 = safeParse('undefined', null); // Returns null
// Detailed error handling
function parseWithDetails(jsonString) {
try {
return {
success: true,
data: JSON.parse(jsonString),
};
} catch (error) {
// Extract error position
const match = error.message.match(/position (\d+)/);
const position = match ? parseInt(match[1]) : null;
return {
success: false,
error: error.message,
position,
preview: position
? jsonString.substring(Math.max(0, position - 20), position + 20)
: null,
};
}
}
JSON.stringify()
Basic Stringification
// Convert JavaScript object to JSON string
const obj = {
name: 'John',
age: 30,
city: 'New York',
hobbies: ['reading', 'gaming'],
};
const jsonString = JSON.stringify(obj);
console.log(jsonString);
// {"name":"John","age":30,"city":"New York","hobbies":["reading","gaming"]}
// Pretty printing with indentation
const prettyJson = JSON.stringify(obj, null, 2);
console.log(prettyJson);
/*
{
"name": "John",
"age": 30,
"city": "New York",
"hobbies": [
"reading",
"gaming"
]
}
*/
// Custom indentation
const customIndent = JSON.stringify(obj, null, '\t'); // Tab indentation
const customString = JSON.stringify(obj, null, '..'); // Custom string
Replacer Function
// Replacer function to filter/transform values
const user = {
id: 1,
name: 'John',
password: 'secret123',
email: 'john@example.com',
createdAt: new Date(),
};
// Filter out sensitive data
const filtered = JSON.stringify(user, (key, value) => {
if (key === 'password') return undefined;
return value;
});
console.log(filtered);
// {"id":1,"name":"John","email":"john@example.com","createdAt":"2024-03-15T10:30:00.000Z"}
// Array as replacer (whitelist properties)
const whitelist = JSON.stringify(user, ['id', 'name', 'email']);
console.log(whitelist);
// {"id":1,"name":"John","email":"john@example.com"}
// Transform values
const transformed = JSON.stringify(user, (key, value) => {
if (typeof value === 'string') {
return value.toUpperCase();
}
if (value instanceof Date) {
return value.toISOString().split('T')[0]; // Date only
}
return value;
});
Handling Special Values
// Special values in JSON.stringify
const special = {
undefined: undefined,
function: function () {},
symbol: Symbol('test'),
nan: NaN,
infinity: Infinity,
negInfinity: -Infinity,
date: new Date(),
regex: /test/gi,
error: new Error('Test error'),
};
console.log(JSON.stringify(special));
// {"nan":null,"infinity":null,"negInfinity":null,"date":"2024-03-15T10:30:00.000Z","regex":{},"error":{}}
// Custom toJSON method
class User {
constructor(name, email, password) {
this.name = name;
this.email = email;
this.password = password;
}
toJSON() {
return {
name: this.name,
email: this.email,
// Password is excluded
};
}
}
const user = new User('John', 'john@example.com', 'secret123');
console.log(JSON.stringify(user));
// {"name":"John","email":"john@example.com"}
Working with APIs
Fetching JSON Data
// GET request for JSON data
async function fetchUserData(userId) {
try {
const response = await fetch(`https://api.example.com/users/${userId}`);
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
const data = await response.json();
return data;
} catch (error) {
console.error('Error fetching user data:', error);
throw error;
}
}
// POST request with JSON body
async function createUser(userData) {
try {
const response = await fetch('https://api.example.com/users', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(userData),
});
if (!response.ok) {
const errorData = await response.json();
throw new Error(errorData.message || 'Failed to create user');
}
return await response.json();
} catch (error) {
console.error('Error creating user:', error);
throw error;
}
}
// Handling different response types
async function smartFetch(url) {
const response = await fetch(url);
const contentType = response.headers.get('content-type');
if (contentType && contentType.includes('application/json')) {
return await response.json();
} else if (contentType && contentType.includes('text/')) {
return await response.text();
} else {
return await response.blob();
}
}
API Response Handling
// Response wrapper class
class ApiResponse {
constructor(response) {
this.ok = response.ok;
this.status = response.status;
this.statusText = response.statusText;
this.headers = response.headers;
}
async parseJson() {
try {
this.data = await this.response.json();
} catch (error) {
this.error = 'Invalid JSON response';
this.data = null;
}
return this;
}
hasError() {
return !this.ok || this.error;
}
getError() {
if (this.error) return this.error;
if (!this.ok) return `HTTP ${this.status}: ${this.statusText}`;
return null;
}
}
// Batch API requests
async function batchFetch(urls) {
const promises = urls.map((url) =>
fetch(url)
.then((res) => res.json())
.catch((error) => ({ error: error.message, url }))
);
return Promise.all(promises);
}
// Retry mechanism for failed requests
async function fetchWithRetry(url, options = {}, maxRetries = 3) {
for (let i = 0; i < maxRetries; i++) {
try {
const response = await fetch(url, options);
if (response.ok) {
return await response.json();
}
throw new Error(`HTTP ${response.status}`);
} catch (error) {
if (i === maxRetries - 1) throw error;
await new Promise((resolve) =>
setTimeout(resolve, 1000 * Math.pow(2, i))
);
}
}
}
Data Validation
JSON Schema Validation
// Simple JSON validator
class JsonValidator {
static validate(data, schema) {
const errors = [];
for (const [key, rules] of Object.entries(schema)) {
if (rules.required && !(key in data)) {
errors.push(`Missing required field: ${key}`);
continue;
}
if (key in data) {
const value = data[key];
if (rules.type && typeof value !== rules.type) {
errors.push(
`Invalid type for ${key}: expected ${rules.type}, got ${typeof value}`
);
}
if (rules.min !== undefined && value < rules.min) {
errors.push(`${key} must be at least ${rules.min}`);
}
if (rules.max !== undefined && value > rules.max) {
errors.push(`${key} must be at most ${rules.max}`);
}
if (rules.pattern && !rules.pattern.test(value)) {
errors.push(`${key} does not match required pattern`);
}
if (rules.enum && !rules.enum.includes(value)) {
errors.push(`${key} must be one of: ${rules.enum.join(', ')}`);
}
}
}
return {
valid: errors.length === 0,
errors,
};
}
}
// Usage
const userSchema = {
name: { type: 'string', required: true, min: 2 },
age: { type: 'number', required: true, min: 0, max: 150 },
email: {
type: 'string',
required: true,
pattern: /^[^\s@]+@[^\s@]+\.[^\s@]+$/,
},
role: { type: 'string', enum: ['user', 'admin', 'moderator'] },
};
const userData = {
name: 'John',
age: 30,
email: 'john@example.com',
role: 'admin',
};
const validation = JsonValidator.validate(userData, userSchema);
console.log(validation); // { valid: true, errors: [] }
Type-Safe JSON Parsing
// Type guards for JSON data
class TypedJSON {
static isString(value) {
return typeof value === 'string';
}
static isNumber(value) {
return typeof value === 'number' && !isNaN(value);
}
static isBoolean(value) {
return typeof value === 'boolean';
}
static isArray(value) {
return Array.isArray(value);
}
static isObject(value) {
return value !== null && typeof value === 'object' && !Array.isArray(value);
}
static parseAs(jsonString, type) {
const data = JSON.parse(jsonString);
switch (type) {
case 'string':
if (!this.isString(data)) throw new TypeError('Expected string');
break;
case 'number':
if (!this.isNumber(data)) throw new TypeError('Expected number');
break;
case 'boolean':
if (!this.isBoolean(data)) throw new TypeError('Expected boolean');
break;
case 'array':
if (!this.isArray(data)) throw new TypeError('Expected array');
break;
case 'object':
if (!this.isObject(data)) throw new TypeError('Expected object');
break;
}
return data;
}
static safeParse(jsonString, fallback = null) {
try {
return { success: true, data: JSON.parse(jsonString) };
} catch (error) {
return { success: false, error: error.message, data: fallback };
}
}
}
Advanced JSON Techniques
Streaming JSON
// Parse large JSON files in chunks
class JSONStream {
constructor() {
this.buffer = '';
this.stack = [];
}
write(chunk) {
this.buffer += chunk;
return this.parse();
}
parse() {
const results = [];
let depth = 0;
let inString = false;
let start = 0;
for (let i = 0; i < this.buffer.length; i++) {
const char = this.buffer[i];
const prevChar = i > 0 ? this.buffer[i - 1] : '';
if (char === '"' && prevChar !== '\\') {
inString = !inString;
}
if (!inString) {
if (char === '{' || char === '[') {
if (depth === 0) start = i;
depth++;
} else if (char === '}' || char === ']') {
depth--;
if (depth === 0) {
const json = this.buffer.substring(start, i + 1);
try {
results.push(JSON.parse(json));
} catch (e) {
// Invalid JSON, skip
}
}
}
}
}
// Keep unparsed data in buffer
if (depth === 0) {
this.buffer = this.buffer.substring(start);
}
return results;
}
}
// JSON Lines (JSONL) parser
function parseJSONLines(text) {
return text
.split('\n')
.filter((line) => line.trim())
.map((line) => {
try {
return JSON.parse(line);
} catch (e) {
console.error('Invalid JSON line:', line);
return null;
}
})
.filter((item) => item !== null);
}
JSON Diff and Patch
// Compare two JSON objects
function jsonDiff(obj1, obj2, path = '') {
const changes = [];
// Check for additions and modifications
for (const key in obj2) {
const newPath = path ? `${path}.${key}` : key;
if (!(key in obj1)) {
changes.push({ type: 'add', path: newPath, value: obj2[key] });
} else if (typeof obj1[key] !== typeof obj2[key]) {
changes.push({
type: 'modify',
path: newPath,
oldValue: obj1[key],
newValue: obj2[key],
});
} else if (typeof obj2[key] === 'object' && obj2[key] !== null) {
if (Array.isArray(obj2[key])) {
if (JSON.stringify(obj1[key]) !== JSON.stringify(obj2[key])) {
changes.push({
type: 'modify',
path: newPath,
oldValue: obj1[key],
newValue: obj2[key],
});
}
} else {
changes.push(...jsonDiff(obj1[key], obj2[key], newPath));
}
} else if (obj1[key] !== obj2[key]) {
changes.push({
type: 'modify',
path: newPath,
oldValue: obj1[key],
newValue: obj2[key],
});
}
}
// Check for deletions
for (const key in obj1) {
if (!(key in obj2)) {
const newPath = path ? `${path}.${key}` : key;
changes.push({ type: 'delete', path: newPath, oldValue: obj1[key] });
}
}
return changes;
}
// Apply JSON patch
function applyPatch(obj, changes) {
const result = JSON.parse(JSON.stringify(obj)); // Deep clone
changes.forEach((change) => {
const keys = change.path.split('.');
const lastKey = keys.pop();
let current = result;
// Navigate to the parent object
for (const key of keys) {
if (!(key in current)) {
current[key] = {};
}
current = current[key];
}
// Apply the change
switch (change.type) {
case 'add':
case 'modify':
current[lastKey] = change.value || change.newValue;
break;
case 'delete':
delete current[lastKey];
break;
}
});
return result;
}
JSON Compression
// Simple JSON compression by removing whitespace
function compressJSON(json) {
return JSON.stringify(JSON.parse(json));
}
// JSON minification with key mapping
class JSONCompressor {
constructor() {
this.keyMap = new Map();
this.reverseMap = new Map();
this.keyCounter = 0;
}
compress(obj) {
return JSON.stringify(this.compressObject(obj));
}
compressObject(obj) {
if (Array.isArray(obj)) {
return obj.map((item) => this.compressObject(item));
}
if (typeof obj === 'object' && obj !== null) {
const compressed = {};
for (const [key, value] of Object.entries(obj)) {
const compressedKey = this.getCompressedKey(key);
compressed[compressedKey] = this.compressObject(value);
}
return compressed;
}
return obj;
}
getCompressedKey(key) {
if (!this.keyMap.has(key)) {
const compressed = `_${this.keyCounter++}`;
this.keyMap.set(key, compressed);
this.reverseMap.set(compressed, key);
}
return this.keyMap.get(key);
}
decompress(json) {
return this.decompressObject(JSON.parse(json));
}
decompressObject(obj) {
if (Array.isArray(obj)) {
return obj.map((item) => this.decompressObject(item));
}
if (typeof obj === 'object' && obj !== null) {
const decompressed = {};
for (const [key, value] of Object.entries(obj)) {
const originalKey = this.reverseMap.get(key) || key;
decompressed[originalKey] = this.decompressObject(value);
}
return decompressed;
}
return obj;
}
getMapping() {
return Object.fromEntries(this.keyMap);
}
}
JSON Security
// Sanitize JSON to prevent injection attacks
class JSONSecurity {
static sanitize(obj, maxDepth = 10, currentDepth = 0) {
if (currentDepth > maxDepth) {
throw new Error('Maximum depth exceeded');
}
if (Array.isArray(obj)) {
return obj.map((item) => this.sanitize(item, maxDepth, currentDepth + 1));
}
if (typeof obj === 'object' && obj !== null) {
const sanitized = {};
for (const [key, value] of Object.entries(obj)) {
// Remove potentially dangerous keys
if (this.isDangerousKey(key)) continue;
// Sanitize the key
const safeKey = this.sanitizeKey(key);
sanitized[safeKey] = this.sanitize(value, maxDepth, currentDepth + 1);
}
return sanitized;
}
// Sanitize string values
if (typeof obj === 'string') {
return this.sanitizeString(obj);
}
return obj;
}
static isDangerousKey(key) {
const dangerous = ['__proto__', 'constructor', 'prototype'];
return dangerous.includes(key.toLowerCase());
}
static sanitizeKey(key) {
return key.replace(/[^a-zA-Z0-9_]/g, '_');
}
static sanitizeString(str) {
return str
.replace(/</g, '<')
.replace(/>/g, '>')
.replace(/"/g, '"')
.replace(/'/g, ''')
.replace(/\//g, '/');
}
static validateJSON(jsonString, maxSize = 1024 * 1024) {
// 1MB default
if (jsonString.length > maxSize) {
throw new Error('JSON string exceeds maximum size');
}
try {
JSON.parse(jsonString);
return true;
} catch (error) {
return false;
}
}
}
// Prevent JSON injection in templates
function safeJSONInject(data) {
const json = JSON.stringify(data);
// Escape for safe inclusion in HTML
return json
.replace(/</g, '\\u003c')
.replace(/>/g, '\\u003e')
.replace(/&/g, '\\u0026');
}
Best Practices
-
Always validate JSON data
function parseJSON(text) { try { return JSON.parse(text); } catch (e) { console.error('Invalid JSON:', e); return null; } }
-
Use proper Content-Type headers
fetch('/api/data', { headers: { 'Content-Type': 'application/json', Accept: 'application/json', }, });
-
Handle circular references
function stringify(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; }); }
-
Consider performance for large data
// Use streaming for large files // Consider compression // Paginate API responses
Conclusion
JSON is essential for modern JavaScript development:
- Data interchange between client and server
- Configuration files and settings
- API communication standard
- Data storage in various formats
- Cross-platform compatibility
Key takeaways:
- Always validate and sanitize JSON data
- Handle parsing errors gracefully
- Use appropriate methods for different scenarios
- Consider security implications
- Optimize for performance with large datasets
- Follow REST API conventions
Master JSON to build robust data-driven applications!