Docs

README

11.2 Working with JSON

šŸ“‹ Table of Contents

  1. •JSON Overview
  2. •JSON.stringify()
  3. •JSON.parse()
  4. •Handling Special Types
  5. •Deep Cloning
  6. •JSON Schema Validation
  7. •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);
ParameterDescription
valueThe value to convert
replacerFunction or array to filter properties
spaceNumber 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);
ParameterDescription
textThe JSON string to parse
reviverFunction 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

ConceptKey Points
stringifyConverts JS to JSON string, loses functions/undefined/symbols
parseConverts JSON string to JS, throws on invalid JSON
replacerFilter/transform values during stringify
reviverTransform values during parse (bottom-up)
toJSONCustom stringify behavior for objects
DatesBecome ISO strings, need manual revival
Circular refsThrow error, need custom handling
Deep cloneJSON method loses types, use structuredClone
README - JavaScript Tutorial | DeepML