Command Palette

Search for a command to run...

Hectal
PHASE 6Beginner ~25 min· topic 6 of 7

Topic 6.6

JSON

In one line

JSON is a text format for data that looks like JavaScript objects; JSON.stringify turns values into JSON text and JSON.parse turns the text back into values, with rules about what survives, plus options for pretty printing and for filtering or reviving values.

Think of it like this

Posting a toy to a friend. You can't post the assembled toy, so you take it apart and write instructions (stringify), send the paper, and your friend builds it again from the instructions (parse). Some parts can't be written down, like the batteries' charge (functions) or a sticker that says "made today" (a date becomes plain text). What arrives is a copy, never the original toy.

Words you'll meet

New words in this topic, in plain English. Come back here whenever one feels fuzzy.

JSON
JavaScript Object Notation: a strict text format for data, used by APIs, files and storage.
Serialise
Turn a value into text (or bytes) so it can be sent or saved. JSON.stringify serialises.
Parse
Read text and build values from it. JSON.parse parses JSON text.
Replacer
The second argument of stringify: a list of keys to keep or a function that changes values.
Reviver
The second argument of parse: a function that can change each value as it is rebuilt.
toJSON
A method an object can have to choose what stringify writes for it.
Circular reference
An object that contains itself somewhere inside, like a.self = a. JSON can't write it.
ISO string
A standard date text like 2024-01-15T10:30:00.000Z.

Step by step

01Turn data into text

stringify gives a string. Keys get double quotes, and the result is one compact line.

app.jswhole filejavascript
const user = { name: "Asha", age: 14 };
const text = JSON.stringify(user);
console.log(text); // {"name":"Asha","age":14}

02Turn text back into data

parse builds a brand new object from the text. It has the same data, but it isn't the original object.

app.jswhole filejavascript
const copy = JSON.parse(text);
console.log(copy.name);     // Asha
console.log(copy === user); // false
Turn text back into datadiagram
Rendering diagram…

03Save and load from localStorage

localStorage can only store strings, so you stringify on the way in and parse on the way out. Guard the parse: stored text can be missing (null) or broken. (In Hectal's preview, storage lasts only until the next Run.)

app.jswhole filejavascript
localStorage.setItem("settings", JSON.stringify({ theme: "dark" }));

function loadSettings() {
  try {
    return JSON.parse(localStorage.getItem("settings")) ?? {};
  } catch {
    return {};
  }
}

04Read JSON from a server

fetch (Topic 11.2) gets a response; response.json() reads the body and parses it as JSON for you. If the body isn't valid JSON it rejects with a SyntaxError, just like JSON.parse.

app.jswhole filejavascript
const response = await fetch("/api/user/7");
const user = await response.json();
console.log(user.name);

05Control what gets written

A toJSON method decides an object's JSON form. Here a money object writes itself as a simple string.

app.jswhole filejavascript
const price = {
  amount: 150,
  currency: "INR",
  toJSON() {
    return this.amount + " " + this.currency;
  },
};
console.log(JSON.stringify({ price })); // {"price":"150 INR"}

Try it yourself

  1. 1

    Pretty print the round trip

    In the first example, change the stringify to JSON.stringify(student, null, 2). Predict the new value of text.length: bigger or smaller? Run to check.

  2. 2

    Add a toJSON

    In the second example, add toJSON() { return "tricky object"; } to tricky. Predict the first line.

  3. 3

    Hide more keys

    In the third example, change the replacer to also drop total. Predict the number of lines in the pretty output.

Code & diagrams

A round trip New tab

The JSON text is a string. Parsing it creates completely new objects and arrays, so nothing is shared with the original.

Preview

Press Run to see the page.

Console

Nothing logged yet.

Expected console output

{"name":"Asha","age":14,"subjects":["maths","art"],"monitor":false,"pet":null}
string 78
art
false false
What JSON can't carry New tab

`missing` and `greet` vanished; inside the array they became `null` so the positions stay the same. The date came back as a string, and the `Set` lost its contents.

Preview

Press Run to see the page.

Console

Nothing logged yet.

Expected console output

{"when":"2024-01-15T10:30:00.000Z","notANumber":null,"big":null,"tags":{},"list":[1,null,null]}
string
TypeError: Do not know how to serialize a BigInt
TypeError
Pretty printing, replacer and reviver New tab

The array replacer kept two keys. The function replacer dropped `cardNumber`, and `2` indented the result (see the page). The reviver turned the date string back into a `Date`.

Preview

Press Run to see the page.

Console

Nothing logged yet.

Expected console output

{"id":42,"total":155}
5
true 10
Invalid JSON throws New tab

Unquoted keys, single quotes, trailing commas and empty text are all invalid JSON. A bare number like `42` is valid. The exact message wording differs between browsers; this one is Chrome's.

Preview

Press Run to see the page.

Console

Nothing logged yet.

Expected console output

parsed: { ok: true }
bad JSON: SyntaxError
bad JSON: SyntaxError
bad JSON: SyntaxError
parsed: 42
Expected property name or '}' in JSON at position 1 (line 1 column 2)

Break it on purpose

Errors are the best teachers. Make each change, read the error, guess what went wrong, then reveal the answer.

Break #1

Parse something that isn't JSON

Call JSON.parse(localStorage.getItem("missing-key") || "undefined"), or any text that isn't JSON, without try...catch.

terminal
$ Console
── what you'll see ──
Uncaught SyntaxError: "undefined" is not valid JSON

Break #2

Expect a Date back

Stringify an object with a Date, parse it, and call back.when.getFullYear().

terminal
$ Console
── what you'll see ──
Uncaught TypeError: back.when.getFullYear is not a function

Myth vs fact

Myth

JSON is the same as a JavaScript object.

Fact

JSON is text. It looks like an object literal but is stricter (double quotes only, no comments, no trailing commas, no functions or undefined). You must parse it to get an object.

Myth

JSON.parse(JSON.stringify(x)) is a good deep copy.

Fact

It loses dates (become strings), undefined, functions, Map, Set, NaN and Infinity, and throws on BigInt and cycles. Use structuredClone (Topic 6.5) for deep copies.

Myth

JSON.parse is dangerous, like eval.

Fact

JSON.parse only builds data and never runs code. eval runs code; never use it to read data.

Pro corner

Extra depth for experienced readers. New to this? Skip it for now and come back later.

  • ▸

    Since ES2019, JSON is a true subset of JavaScript: the characters U+2028 and U+2029 are allowed in JS strings too. JSON.stringify also escapes lone surrogates (well-formed JSON.stringify, ES2019), so output is always valid UTF-8.

  • ▸

    The reviver runs bottom-up: children are revived before their parent, and finally the root with key "". The replacer runs top-down, starting with the root under key "". Both receive this as the object holding the current key.

  • ▸

    Large numbers lose precision: JSON numbers are read as 64-bit floats, so IDs above Number.MAX_SAFE_INTEGER (9007199254740991) change silently. APIs send big IDs as strings for this reason. A newer feature (JSON.rawJSON, plus the original source text passed to revivers) allows exact handling; check browser support before relying on it.

  • ▸

    Embedding JSON inside an HTML <script> needs < escaped (as \u003c), or a value containing </script> would end the script element: a classic cross-site scripting (XSS) risk. Server frameworks provide safe helpers for this.

Remember this

  1. 1

    JSON (JavaScript Object Notation) is a text format for sending and saving data. It looks like JavaScript, but it's stricter: keys must be in double quotes, strings use double quotes, there are no comments and no trailing commas. A JSON value can only be an object, an array, a string, a number, true, false or null. It's the standard language for web APIs, config files and localStorage, and every programming language can read it. (See /system-design for how APIs use it.)

  2. 2

    **JSON.stringify(value) turns a value into a JSON string. JSON.parse(text)** reads a JSON string and builds new values from it. A round trip, JSON.parse(JSON.stringify(obj)), gives you a new object with the same data, never the same object. If the text isn't valid JSON, JSON.parse throws a SyntaxError, so wrap it in try...catch whenever the text comes from outside (a server, storage, user input).

  3. 3

    Some values can't be written in JSON, and stringify handles each in a fixed way. Properties whose value is undefined, a function or a symbol are left out of objects (and become null inside arrays). NaN and Infinity become null. A Date becomes an ISO string (it has a toJSON method), and parsing does not turn it back into a date. Map and Set become {} (their data is lost). A BigInt throws a TypeError, and so does an object that contains itself (a circular reference).

  4. 4

    Pretty printing: the third argument of stringify is the indentation, a number of spaces (up to 10) or a string like "\t": JSON.stringify(data, null, 2). The output gets new lines and indentation, perfect for showing data on a page, logging or saving readable files. Without it, JSON is one compact line, best for sending over the network.

  5. 5

    The second argument of stringify is a replacer. An array of keys acts as an allowlist: JSON.stringify(user, ["id", "name"]) keeps only those keys. A function (key, value) => ... is called for every property: return the value to keep it, a different value to change it, or undefined to drop it (great for hiding passwords). JSON.parse takes a reviver function in the same shape, called for each value as it's built, which is how you turn date strings back into Date objects.

  6. 6

    Any object can control its own JSON with a **toJSON() method**: stringify calls it and uses what it returns. That's how Date works. Always prefer JSON.parse over eval for reading data: eval runs any code inside the text, which is a security hole; JSON.parse only ever builds data.

Explain it without notes

01

What is JSON, and how is it different from a JavaScript object literal?

02

What happens to undefined, functions, NaN, Date, Map and BigInt in JSON.stringify?

03

How do the replacer and reviver arguments work? Give a use for each.

04

How should you safely read JSON from localStorage or an API?

Practice

01

Stringify { title: "Notes", pages: 40, draft: undefined } with two-space indentation and log it. Which key is missing and why?

02

Write safeParse(text, fallback) that returns the parsed value, or fallback if the text isn't valid JSON. Test it with '[1,2]' and '[1,2'.

03

Stringify { user: "asha", password: "secret", token: "abc" } so that password and token are written as "***".

Trade-offs

  • ↔

    JSON is readable, universal and built in, but it has no dates, binary data, big integers or comments; formats like MessagePack or Protocol Buffers are smaller and typed but need libraries and aren't human-readable.

  • ↔

    Pretty-printed JSON is easy to read and diff but bigger; send compact JSON over the network (compression shrinks the difference) and pretty-print for humans.

Done when you can

  • I can convert objects to JSON text and back.

  • I know what stringify does with undefined, functions, NaN, dates, Map and BigInt.

  • I can pretty-print JSON with indentation.

  • I can use a replacer to hide or change values and a reviver to restore dates.

  • I always wrap JSON.parse of outside data in try...catch.