Follow AiTechWorlds on LinkedIn for professional AI content!Follow Now →
16 minLesson 16 of 35
Arrays & Objects

JSON: Parse, Stringify & Working with APIs

JSON: Parse, Stringify & Working with APIs

JSON (JavaScript Object Notation) is the universal format for data exchange on the web. Every API you call sends and receives JSON. Understanding it deeply — including its quirks and limitations — is non-negotiable for web development.

JSON Syntax

JSON looks like JavaScript objects but has stricter rules:

{
  "name": "Alice",
  "age": 30,
  "active": true,
  "scores": [95, 87, 92],
  "address": {
    "city": "New York",
    "zip": "10001"
  },
  "notes": null
}

JSON rules:

  • Keys must be double-quoted strings
  • Values: string, number, boolean, array, object, or null
  • No trailing commas
  • No comments
  • No undefined, functions, Dates, or symbols

JSON.stringify — Object to String

const user = { name: "Alice", age: 30, active: true };
JSON.stringify(user);
// '{"name":"Alice","age":30,"active":true}'

// Pretty-print (3rd argument is indent spaces)
JSON.stringify(user, null, 2);
// {
//   "name": "Alice",
//   "age": 30,
//   "active": true
// }

// What gets dropped:
const messy = {
    name: "Alice",
    fn: () => "hello",     // ← dropped (functions)
    undef: undefined,      // ← dropped (undefined)
    sym: Symbol("id"),     // ← dropped (symbols)
    date: new Date(),      // ← converted to ISO string
    num: Infinity,         // ← becomes null
    nan: NaN               // ← becomes null
};

JSON.stringify(messy, null, 2);
// { "name": "Alice", "date": "2026-05-26T...", "num": null, "nan": null }

Replacer Function

// Only include specific keys
JSON.stringify(user, ["name", "active"]);
// '{"name":"Alice","active":true}'

// Transform values during serialization
const data = { name: "Alice", password: "secret123", age: 30 };

JSON.stringify(data, (key, value) => {
    if (key === "password") return undefined;  // omit
    return value;
});
// '{"name":"Alice","age":30}'

toJSON Method

Objects can define how they serialize themselves:

class User {
    constructor(name, password) {
        this.name = name;
        this.password = password;
    }
    
    toJSON() {
        // Only expose safe fields
        return { name: this.name };
    }
}

JSON.stringify(new User("Alice", "secret"));
// '{"name":"Alice"}'

JSON.parse — String to Object

const json = '{"name":"Alice","age":30,"scores":[95,87]}';
const user = JSON.parse(json);

user.name;      // "Alice"
user.scores[0]; // 95

// With reviver function — transform values during parsing
const dateJson = '{"name":"Alice","createdAt":"2026-01-15T10:30:00.000Z"}';

const parsed = JSON.parse(dateJson, (key, value) => {
    if (key === "createdAt") return new Date(value);  // convert to Date object
    return value;
});

parsed.createdAt instanceof Date;  // true
parsed.createdAt.getFullYear();    // 2026

Error Handling

JSON.parse throws on invalid JSON. Always wrap it:

function safeParseJSON(str, fallback = null) {
    try {
        return JSON.parse(str);
    } catch {
        return fallback;
    }
}

safeParseJSON('{"valid": true}');   // { valid: true }
safeParseJSON('not json at all');   // null
safeParseJSON('undefined', {});     // {}

// In practice — API responses
async function fetchUser(id) {
    const res = await fetch(`/api/users/${id}`);
    
    if (!res.ok) {
        const errorText = await res.text();
        const error = safeParseJSON(errorText, { message: errorText });
        throw new Error(error.message ?? "Request failed");
    }
    
    return res.json();  // fetch's .json() already parses + throws on bad JSON
}

Working with APIs

// GET request — receive JSON
const response = await fetch("https://api.example.com/users");
const users = await response.json();  // parses JSON automatically

// POST request — send JSON
const newUser = { name: "Alice", email: "alice@example.com" };

const response = await fetch("https://api.example.com/users", {
    method: "POST",
    headers: {
        "Content-Type": "application/json",
        "Authorization": `Bearer ${token}`
    },
    body: JSON.stringify(newUser)  // serialize before sending
});

const created = await response.json();
console.log(created.id);  // the ID assigned by the server

Local Storage with JSON

localStorage only stores strings, so JSON is used to serialize/deserialize:

// Save
const cart = [{ id: 1, qty: 2 }, { id: 3, qty: 1 }];
localStorage.setItem("cart", JSON.stringify(cart));

// Load
const saved = localStorage.getItem("cart");
const cart = saved ? JSON.parse(saved) : [];

// Utility wrapper
const storage = {
    get(key, fallback = null) {
        return safeParseJSON(localStorage.getItem(key), fallback);
    },
    set(key, value) {
        localStorage.setItem(key, JSON.stringify(value));
    },
    remove(key) {
        localStorage.removeItem(key);
    }
};

storage.set("preferences", { theme: "dark", language: "en" });
storage.get("preferences");  // { theme: "dark", language: "en" }

Deep Cloning with JSON

A quick way to deep clone objects — but with limitations:

// Works for: strings, numbers, booleans, arrays, plain objects
const original = { a: 1, b: { c: 2 }, d: [3, 4] };
const clone = JSON.parse(JSON.stringify(original));

clone.b.c = 99;
original.b.c;  // 2 — unaffected

// Does NOT work for: Dates, functions, undefined, circular references
const withDate = { date: new Date(), fn: () => {} };
JSON.parse(JSON.stringify(withDate));
// { date: "2026-05-26T..." }  — Date becomes string, fn is dropped!

// Better alternatives:
// structuredClone (built-in, handles more types)
const clone2 = structuredClone(original);

// lodash deepClone
import cloneDeep from "lodash/cloneDeep";
const clone3 = cloneDeep(withDate);  // preserves Dates, handles circular refs

Next lesson: DOM Manipulation — selecting and modifying HTML elements from JavaScript.

📱

Get this course's notes on Telegram!

Free cheat sheets, summaries & practice exercises

Get Notes Free →
!