📊 Working with JSON

JavaScript Object Notation

What is JSON?

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. It's the standard format for data exchange in web APIs.

📝 JSON Syntax

// Valid JSON structure
{
    "name": "John Doe",
    "age": 30,
    "email": "john@example.com",
    "active": true,
    "address": {
        "street": "123 Main St",
        "city": "New York",
        "zipCode": "10001"
    },
    "hobbies": ["reading", "coding", "gaming"],
    "salary": null
}

// JSON Rules:
// ✅ Property names must be double-quoted strings
// ✅ String values must use double quotes
// ✅ Numbers: integers or decimals (no NaN, Infinity)
// ✅ Booleans: true or false (lowercase)
// ✅ Arrays: ordered lists
// ✅ Objects: key-value pairs
// ✅ null: represents absence of value
// ❌ No functions, undefined, Date objects, Symbol
// ❌ No comments allowed
// ❌ No trailing commas

// Invalid JSON examples:
{
    name: "John",           // ❌ unquoted key
    'age': 30,              // ❌ single quotes
    "hobbies": ['coding'],  // ❌ single quotes in array
    "date": new Date(),     // ❌ Date object
    "func": function() {}   // ❌ function
}

🔄 JSON.parse()

Basic Parsing

// Parse JSON string to JavaScript object
let jsonString = '{"name":"John","age":30}';
let obj = JSON.parse(jsonString);

console.log(obj.name);  // 'John'
console.log(obj.age);   // 30
console.log(typeof obj);  // 'object'

// Parse arrays
let jsonArray = '[1, 2, 3, 4, 5]';
let arr = JSON.parse(jsonArray);
console.log(arr[0]);  // 1

// Parse nested structures
let json = `{
    "user": {
        "name": "John",
        "address": {
            "city": "New York"
        }
    }
}`;

let data = JSON.parse(json);
console.log(data.user.address.city);  // 'New York'

// Parse primitive values
JSON.parse('true');      // true
JSON.parse('123');       // 123
JSON.parse('"hello"');   // 'hello'
JSON.parse('null');      // null

Error Handling

// JSON.parse() throws on invalid JSON
try {
    let obj = JSON.parse('{ invalid json }');
} catch (error) {
    console.error('Parse error:', error.message);
    // SyntaxError: Unexpected token i in JSON
}

// Safe parsing function
function safeJSONParse(json, defaultValue = null) {
    try {
        return JSON.parse(json);
    } catch (error) {
        console.error('Invalid JSON:', error.message);
        return defaultValue;
    }
}

let data = safeJSONParse('{ invalid }', {});

// Common parse errors
JSON.parse("{'key': 'value'}");  // ❌ Single quotes
JSON.parse('{key: "value"}');    // ❌ Unquoted key
JSON.parse('{"key": undefined}');  // ❌ undefined
JSON.parse('{"key": "value",}');  // ❌ Trailing comma
JSON.parse('NaN');               // ❌ NaN not valid
JSON.parse('Infinity');          // ❌ Infinity not valid

// Valid alternatives
JSON.parse('{"key": "value"}');  // ✅
JSON.parse('{"key": null}');     // ✅
JSON.parse('{"num": 123}');      // ✅

Reviver Function

// Transform values while parsing
let json = '{"name":"John","age":"30"}';

let obj = JSON.parse(json, (key, value) => {
    console.log(`Key: ${key}, Value: ${value}`);
    
    // Convert age string to number
    if (key === 'age') {
        return parseInt(value);
    }
    
    return value;
});

console.log(typeof obj.age);  // 'number'

// Parse dates from JSON
let json = '{"created":"2024-01-15T10:30:00.000Z"}';

let obj = JSON.parse(json, (key, value) => {
    if (typeof value === 'string' && /^\d{4}-/.test(value)) {
        let date = new Date(value);
        if (!isNaN(date)) {
            return date;
        }
    }
    return value;
});

console.log(obj.created instanceof Date);  // true

// Filter out properties
let json = '{"name":"John","password":"secret","age":30}';

let obj = JSON.parse(json, (key, value) => {
    if (key === 'password') {
        return undefined;  // Exclude property
    }
    return value;
});

console.log(obj);  // { name: 'John', age: 30 }

// Convert special values
let json = '{"value":"__undefined__"}';

let obj = JSON.parse(json, (key, value) => {
    if (value === '__undefined__') {
        return undefined;
    }
    return value;
});

📤 JSON.stringify()

Basic Stringification

// Convert JavaScript object to JSON string
let obj = {
    name: 'John',
    age: 30,
    active: true
};

let json = JSON.stringify(obj);
console.log(json);  // '{"name":"John","age":30,"active":true}'

// Stringify arrays
let arr = [1, 2, 3, 4, 5];
console.log(JSON.stringify(arr));  // '[1,2,3,4,5]'

// Stringify nested structures
let data = {
    user: {
        name: 'John',
        address: {
            city: 'New York'
        }
    }
};

console.log(JSON.stringify(data));
// '{"user":{"name":"John","address":{"city":"New York"}}}'

// Stringify primitive values
JSON.stringify(true);      // 'true'
JSON.stringify(123);       // '123'
JSON.stringify('hello');   // '"hello"'
JSON.stringify(null);      // 'null'

Handling Special Values

// undefined becomes undefined (omitted in objects)
let obj = { a: 1, b: undefined, c: 3 };
JSON.stringify(obj);  // '{"a":1,"c":3}'

// undefined in arrays becomes null
let arr = [1, undefined, 3];
JSON.stringify(arr);  // '[1,null,3]'

// Functions are omitted
let obj = {
    name: 'John',
    greet: function() { return 'Hi'; }
};
JSON.stringify(obj);  // '{"name":"John"}'

// Symbols are omitted
let obj = {
    name: 'John',
    [Symbol('id')]: 123
};
JSON.stringify(obj);  // '{"name":"John"}'

// Dates become ISO strings
let obj = {
    created: new Date('2024-01-15')
};
JSON.stringify(obj);  // '{"created":"2024-01-15T00:00:00.000Z"}'

// NaN and Infinity become null
let obj = {
    a: NaN,
    b: Infinity,
    c: -Infinity
};
JSON.stringify(obj);  // '{"a":null,"b":null,"c":null}'

// Circular references throw error
let obj = {};
obj.self = obj;
JSON.stringify(obj);  // TypeError: Converting circular structure

Replacer Function

// Transform values while stringifying
let user = {
    name: 'John',
    password: 'secret123',
    age: 30
};

let json = JSON.stringify(user, (key, value) => {
    // Hide password
    if (key === 'password') {
        return undefined;
    }
    return value;
});

console.log(json);  // '{"name":"John","age":30}'

// Convert dates to custom format
let obj = {
    name: 'John',
    created: new Date('2024-01-15')
};

let json = JSON.stringify(obj, (key, value) => {
    if (value instanceof Date) {
        return value.toLocaleDateString();
    }
    return value;
});

// Uppercase string values
let obj = { name: 'john', city: 'new york' };

let json = JSON.stringify(obj, (key, value) => {
    if (typeof value === 'string') {
        return value.toUpperCase();
    }
    return value;
});

console.log(json);  // '{"name":"JOHN","city":"NEW YORK"}'

// Array replacer (whitelist properties)
let user = {
    name: 'John',
    email: 'john@example.com',
    password: 'secret',
    age: 30
};

let json = JSON.stringify(user, ['name', 'email']);
console.log(json);  // '{"name":"John","email":"john@example.com"}'

Pretty Printing

// Third parameter: space for indentation
let obj = {
    name: 'John',
    age: 30,
    address: {
        city: 'New York',
        zip: '10001'
    }
};

// Number of spaces
let json = JSON.stringify(obj, null, 2);
console.log(json);
/*
{
  "name": "John",
  "age": 30,
  "address": {
    "city": "New York",
    "zip": "10001"
  }
}
*/

// Custom indentation string
let json = JSON.stringify(obj, null, '\t');

// 4 spaces
let json = JSON.stringify(obj, null, 4);

// Max 10 characters for space parameter
let json = JSON.stringify(obj, null, '          ');

🔧 Common Patterns

Deep Clone

// Simple deep clone using JSON
let original = {
    name: 'John',
    age: 30,
    address: {
        city: 'New York'
    },
    hobbies: ['coding', 'reading']
};

let clone = JSON.parse(JSON.stringify(original));

clone.address.city = 'Boston';
console.log(original.address.city);  // 'New York' (unchanged)

// Limitations of JSON clone:
// ❌ Loses functions
// ❌ Loses undefined values
// ❌ Loses Symbols
// ❌ Loses Date objects (becomes string)
// ❌ Can't handle circular references
// ❌ Loses Map, Set, RegExp, Error

let obj = {
    date: new Date(),
    func: () => {},
    undef: undefined,
    map: new Map([['key', 'value']])
};

let clone = JSON.parse(JSON.stringify(obj));
console.log(clone);
// { date: '2024-01-15T...' } - only date as string

// Better alternatives:
// structuredClone (modern browsers)
let clone = structuredClone(original);

// Or custom deep clone
function deepClone(obj) {
    if (obj === null || typeof obj !== 'object') {
        return obj;
    }
    
    if (obj instanceof Date) {
        return new Date(obj);
    }
    
    if (obj instanceof Array) {
        return obj.map(deepClone);
    }
    
    let clone = {};
    for (let key in obj) {
        if (obj.hasOwnProperty(key)) {
            clone[key] = deepClone(obj[key]);
        }
    }
    return clone;
}

API Request/Response

// Send JSON to API
async function createUser(userData) {
    let response = await fetch('/api/users', {
        method: 'POST',
        headers: {
            'Content-Type': 'application/json'
        },
        body: JSON.stringify(userData)
    });
    
    if (!response.ok) {
        throw new Error(`HTTP ${response.status}`);
    }
    
    return response.json();
}

// Usage
try {
    let user = await createUser({
        name: 'John',
        email: 'john@example.com'
    });
    console.log('Created:', user);
} catch (error) {
    console.error('Error:', error);
}

// Parse response safely
async function fetchData(url) {
    let response = await fetch(url);
    
    if (!response.ok) {
        throw new Error(`HTTP ${response.status}`);
    }
    
    let text = await response.text();
    
    try {
        return JSON.parse(text);
    } catch (error) {
        console.error('Invalid JSON response:', text);
        throw new Error('Invalid JSON');
    }
}

// Handle different response types
async function apiRequest(url) {
    let response = await fetch(url);
    let contentType = response.headers.get('Content-Type');
    
    if (contentType && contentType.includes('application/json')) {
        return response.json();
    } else {
        return response.text();
    }
}

Local Storage

// Store object in localStorage
function saveToStorage(key, value) {
    try {
        localStorage.setItem(key, JSON.stringify(value));
    } catch (error) {
        console.error('Storage error:', error);
    }
}

function loadFromStorage(key, defaultValue = null) {
    try {
        let item = localStorage.getItem(key);
        return item ? JSON.parse(item) : defaultValue;
    } catch (error) {
        console.error('Parse error:', error);
        return defaultValue;
    }
}

// Usage
saveToStorage('user', { name: 'John', age: 30 });
let user = loadFromStorage('user', {});

// Storage wrapper class
class Storage {
    static set(key, value) {
        try {
            localStorage.setItem(key, JSON.stringify(value));
            return true;
        } catch (error) {
            console.error('Storage error:', error);
            return false;
        }
    }
    
    static get(key, defaultValue = null) {
        try {
            let item = localStorage.getItem(key);
            return item ? JSON.parse(item) : defaultValue;
        } catch (error) {
            console.error('Parse error:', error);
            return defaultValue;
        }
    }
    
    static remove(key) {
        localStorage.removeItem(key);
    }
    
    static clear() {
        localStorage.clear();
    }
}

// Usage
Storage.set('settings', { theme: 'dark', fontSize: 16 });
let settings = Storage.get('settings', {});

Configuration Files

// Load config from JSON file
async function loadConfig() {
    try {
        let response = await fetch('/config.json');
        if (!response.ok) {
            throw new Error('Config not found');
        }
        
        let config = await response.json();
        return config;
    } catch (error) {
        console.error('Config error:', error);
        // Return defaults
        return {
            apiUrl: 'http://localhost:3000',
            timeout: 5000
        };
    }
}

// Usage
let config = await loadConfig();
console.log(config.apiUrl);

// Validate config structure
function validateConfig(config) {
    let required = ['apiUrl', 'timeout'];
    
    for (let key of required) {
        if (!(key in config)) {
            throw new Error(`Missing config: ${key}`);
        }
    }
    
    if (typeof config.timeout !== 'number') {
        throw new Error('timeout must be number');
    }
    
    return config;
}

💡 Practical Examples

Data Transformation

// Transform API response
function transformUser(apiUser) {
    return {
        id: apiUser.user_id,
        name: apiUser.full_name,
        email: apiUser.email_address,
        created: new Date(apiUser.created_at),
        active: apiUser.status === 'active'
    };
}

let apiResponse = {
    user_id: 123,
    full_name: 'John Doe',
    email_address: 'john@example.com',
    created_at: '2024-01-15T10:30:00Z',
    status: 'active'
};

let user = transformUser(apiResponse);

// Batch transformation
function transformUsers(apiUsers) {
    return apiUsers.map(transformUser);
}

// Reverse transformation
function toAPIFormat(user) {
    return {
        user_id: user.id,
        full_name: user.name,
        email_address: user.email,
        created_at: user.created.toISOString(),
        status: user.active ? 'active' : 'inactive'
    };
}

JSON Schema Validation

// Simple validation
function validateUser(data) {
    let errors = [];
    
    if (!data.name || typeof data.name !== 'string') {
        errors.push('name is required and must be string');
    }
    
    if (!data.email || !/\S+@\S+\.\S+/.test(data.email)) {
        errors.push('valid email is required');
    }
    
    if (data.age !== undefined) {
        if (typeof data.age !== 'number' || data.age < 0) {
            errors.push('age must be positive number');
        }
    }
    
    return {
        valid: errors.length === 0,
        errors
    };
}

// Usage
let result = validateUser({ name: 'John', email: 'invalid' });
if (!result.valid) {
    console.error('Validation errors:', result.errors);
}

// Validate with schema
function validate(data, schema) {
    let errors = [];
    
    for (let [key, rules] of Object.entries(schema)) {
        let value = data[key];
        
        if (rules.required && value === undefined) {
            errors.push(`${key} is required`);
            continue;
        }
        
        if (value !== undefined && rules.type) {
            let actualType = Array.isArray(value) ? 'array' : typeof value;
            if (actualType !== rules.type) {
                errors.push(`${key} must be ${rules.type}`);
            }
        }
        
        if (rules.min !== undefined && value < rules.min) {
            errors.push(`${key} must be >= ${rules.min}`);
        }
        
        if (rules.pattern && !rules.pattern.test(value)) {
            errors.push(`${key} is invalid format`);
        }
    }
    
    return { valid: errors.length === 0, errors };
}

// Usage
let schema = {
    name: { required: true, type: 'string' },
    email: { required: true, pattern: /\S+@\S+\.\S+/ },
    age: { type: 'number', min: 0 }
};

let result = validate(data, schema);

Export/Import Data

// Export data as JSON file
function exportData(data, filename) {
    let json = JSON.stringify(data, null, 2);
    let blob = new Blob([json], { type: 'application/json' });
    let url = URL.createObjectURL(blob);
    
    let a = document.createElement('a');
    a.href = url;
    a.download = filename;
    a.click();
    
    URL.revokeObjectURL(url);
}

// Usage
let data = { users: [...], settings: {...} };
exportData(data, 'export.json');

// Import JSON file
function importData(file) {
    return new Promise((resolve, reject) => {
        let reader = new FileReader();
        
        reader.onload = e => {
            try {
                let data = JSON.parse(e.target.result);
                resolve(data);
            } catch (error) {
                reject(new Error('Invalid JSON file'));
            }
        };
        
        reader.onerror = () => {
            reject(new Error('File read error'));
        };
        
        reader.readAsText(file);
    });
}

// Usage
let fileInput = document.querySelector('input[type="file"]');
fileInput.addEventListener('change', async () => {
    let file = fileInput.files[0];
    try {
        let data = await importData(file);
        console.log('Imported:', data);
    } catch (error) {
        console.error('Import error:', error);
    }
});

Diff and Merge

// Compare two objects
function diff(obj1, obj2) {
    let changes = {};
    
    let allKeys = new Set([
        ...Object.keys(obj1),
        ...Object.keys(obj2)
    ]);
    
    for (let key of allKeys) {
        let val1 = obj1[key];
        let val2 = obj2[key];
        
        if (JSON.stringify(val1) !== JSON.stringify(val2)) {
            changes[key] = { old: val1, new: val2 };
        }
    }
    
    return changes;
}

let old = { name: 'John', age: 30, city: 'NYC' };
let new = { name: 'John', age: 31, country: 'USA' };
let changes = diff(old, new);
// { age: {old: 30, new: 31}, city: {old: 'NYC', new: undefined}, country: {old: undefined, new: 'USA'} }

// Deep merge objects
function merge(target, source) {
    for (let key in source) {
        if (source[key] && typeof source[key] === 'object' && !Array.isArray(source[key])) {
            target[key] = merge(target[key] || {}, source[key]);
        } else {
            target[key] = source[key];
        }
    }
    return target;
}

let defaults = { theme: 'light', fontSize: 16, colors: { bg: '#fff' } };
let user = { fontSize: 18, colors: { text: '#000' } };
let config = merge({...defaults}, user);
// { theme: 'light', fontSize: 18, colors: { bg: '#fff', text: '#000' } }

⚡ Performance Tips

// Parse large JSON incrementally
// For huge files, consider streaming parsers

// Stringify performance
// Avoid stringifying large objects repeatedly
// Cache results when possible
let cache = new Map();

function getCachedJSON(key, obj) {
    if (!cache.has(key)) {
        cache.set(key, JSON.stringify(obj));
    }
    return cache.get(key);
}

// Use replacer to limit depth
function limitDepth(obj, maxDepth) {
    return JSON.stringify(obj, (key, value) => {
        if (typeof value === 'object' && value !== null) {
            if (Object.keys(value).length > maxDepth) {
                return '[Object]';
            }
        }
        return value;
    });
}

// Avoid parsing same data multiple times
let cachedData;
function getData() {
    if (!cachedData) {
        cachedData = JSON.parse(largeJSONString);
    }
    return cachedData;
}

🎯 Key Takeaways