cf.completefrontendCode editorOpen lab
THE JAVASCRIPT FIELD GUIDE

JSON

Send and store data as text with JSON.stringify, JSON.parse, replacers, revivers, toJSON, and the newest source-text tools.

You will learn to
  • 01
    Pack and unpack dataUse JSON.stringify and JSON.parse on plain objects and arrays.
  • 02
    Customize the tripFilter with replacers, restore richer values with revivers, and write toJSON methods.
  • 03
    Spot lossy edgesPredict what JSON drops, changes, or refuses, including recent raw JSON features.

JSON as data text

JSON (JavaScript Object Notation) is a small text format for data. It can describe strings, finite numbers, booleans, null, arrays, and plain objects with double-quoted property names. That is why the curriculum summary says JSON helps you send and store data as text.

JSON looks like JavaScript object and array literals, but it is stricter and smaller. It has no comments, no single-quoted strings, no functions, no undefined, no symbols, no BigInt, and no special Date value. The reward for those limits is portability: almost every programming language can read it.

Real-life analogyJSON is a universal shipping label

Imagine sending a package through many countries. The shipping label needs a common, simple language: name, address, weight, yes/no checkboxes. JSON plays that role for programs. It is boring on purpose so other systems can read it without knowing JavaScript.

In real life: Packing an object into a label
In JavaScript: JSON.stringify(value) creates JSON text
In real life: Unpacking on arrival
In JavaScript: JSON.parse(text) creates JavaScript data
In real life: Simple boxes and lists
In JavaScript: Objects and arrays
In real life: Words, counts, yes/no, empty
In JavaScript: strings, numbers, booleans, and null

Where the analogy stops: A shipping label cannot carry the item itself. JSON can describe data, but not functions, object identity, prototypes, private fields, or circular references.

You have already met objects and references, numbers, BigInt, and symbols. This lesson shows how those values behave when they cross the JSON border. Later, the Fetch & JSON lesson in Stage 7 will use the same ideas to read responses from servers.

JSON.stringify and JSON.parse

INTERACTIVE

JSON.stringify(value) packs a supported JavaScript value into JSON text. JSON.parse(text) reads JSON text and returns fresh JavaScript values. Fresh matters: a JSON round trip can act like a deep copy for plain data, but the Objects & references lesson’s warning still applies: it is not a perfect clone.

Pack and unpack a plain object
Step 0 of 5Ready
Your turn: follow the blue line

Predict what changes when an object is packed into JSON text and unpacked again. Then step through the real operations.

Running in
  1. script
Next: line 1
Click the blue line to take the next stepPop out in the code editor (opens in a new tab)JavaScript
  item: "keyboard",  quantity: 2,  shipped: false,}; const text = JSON.stringify(order, null, 2);const copy = JSON.parse(text);console.log(text);console.log(copy.item);
CallStoreChangeResultRun = next line. Ran = already executed.
Recent returnsNothing yet. Start with the blue line.
A guided replay recorded from real JavaScript calls, not an engine debugger. Step follows executed statements; Back reviews a snapshot. Reset starts a fresh run.

The second argument to JSON.stringify can customize values. The third argument controls pretty-printing. Passing 2 means two spaces; passing 0 or omitting it gives compact JSON.

Live JSON workbench
Preset: Nested userPop out in the code editor (opens in a new tab)JavaScript
const value = {  name: "Ava",  address: { city: "Pune", country: "IN" },  roles: ["admin", "editor"],};
Real resultJSON.stringify succeeded
JSON text
{
  "name": "Ava",
  "address": {
    "city": "Pune",
    "country": "IN"
  },
  "roles": [
    "admin",
    "editor"
  ]
}
Parsed back
{
  "name": "Ava",
  "address": {
    "city": "Pune",
    "country": "IN"
  },
  "roles": [
    "admin",
    "editor"
  ]
}
  • Plain objects and arrays round-trip clearly.
Try it yourself

JSON.stringify succeeded. Parse below shows what comes back after the round trip.

This workbench uses real JSON.stringify and JSON.parse in your browser. The replacer removes functions, symbols, and password keys.

Try each preset. A nested user survives; a Date comes back as text; undefined, functions, and symbols disappear or become null; Map and Set become empty objects; BigInt and circular references throw TypeError. Those are real browser results, not hand-written examples.

JSON.parse reads JSON, not loose JavaScript

Valid JSON uses double quotes and has no trailing commas or comments. JSON.parse("{'a':1}"), JSON.parse('{"a":1,}'), and commented text all throw SyntaxError.

Replacer and reviver

STEP THROUGH

A replacer is a function or an array passed to JSON.stringify. A function replacer is called for the wrapper and each property; returning undefined removes an object property. Think of it as a customs officer checking every item before shipping.

Real-life analogyA replacer is a customs officer

Before a parcel leaves, customs can remove unsafe items or rewrite paperwork. A replacer gives you that checkpoint while JSON is being made.

In real life: Inspect every item
In JavaScript: The replacer receives each key and value
In real life: Confiscate passwords
In JavaScript: Return undefined for sensitive properties
In real life: Relabel an item
In JavaScript: Return a different serializable value

Where the analogy stops: Real customs rules know the whole shipment. A replacer only sees one key and value at a time, with this as the holder object.

A replacer filters every key
Step 0 of 9Ready
Your turn: follow the blue line

Step through a replacer function. Watch every key visit, then see passwords disappear from the final JSON.

Running in
  1. script
Next: line 1
Click the blue line to take the next stepPop out in the code editor (opens in a new tab)JavaScript
  name: "Ava",  password: "rosebud",  profile: { password: "also-secret", theme: "dark" },}; const safeText = JSON.stringify(account, (key, value) => {  if (key === "password") return undefined;  return value;}, 2);console.log(safeText);
CallStoreChangeResultRun = next line. Ran = already executed.
Recent returnsNothing yet. Start with the blue line.
A guided replay recorded from real JavaScript calls, not an engine debugger. Step follows executed statements; Back reviews a snapshot. Reset starts a fresh run.

A replacer array is simpler: JSON.stringify(user, ["name", "email"]) whitelists those property names. It is useful for small, flat output, but a function is clearer when you need rules such as “remove every password key.”

A reviver is the matching hook for JSON.parse. It receives each parsed key and value and can return a replacement. A common use is restoring an ISO date string into a Date object. Date basics are in the next module, so here the key idea is only that JSON stores the Date as text.

A reviver restores a Date
Step 0 of 6Ready
Your turn: follow the blue line

Revivers run while parsing. Step through the point where a plain string becomes a Date object.

Running in
  1. script
Next: line 1
Click the blue line to take the next stepPop out in the code editor (opens in a new tab)JavaScript
 const user = JSON.parse(text, (key, value) => {  if (key === "joined") return new Date(value);  return value;}); console.log(user.joined instanceof Date);console.log(user.joined.toISOString());
CallStoreChangeResultRun = next line. Ran = already executed.
Recent returnsNothing yet. Start with the blue line.
A guided replay recorded from real JavaScript calls, not an engine debugger. Step follows executed statements; Back reviews a snapshot. Reset starts a fresh run.

toJSON: an object’s own packing instructions

CODE

Before JSON.stringify writes an object, it checks for a toJSON method. If the method exists, stringify serializes the method’s return value instead of the original object. That is why Dates become ISO strings: Date.prototype.toJSON already exists.

Custom toJSONPop out in the code editor (opens in a new tab)JavaScript
const price = {  cents: 1250,  currency: "USD",  toJSON() {    return "12.50 USD";  },}; console.log(JSON.stringify({ price }));

A class can use this to publish a deliberate data shape. For example, a money object may store cents internally but ship as "12.50 USD". Keep toJSON boring and predictable: it should return JSON-friendly data, not depend on the current time or hidden global state.

Order of operations

toJSON runs before the replacer sees that value. If both exist, the replacer receives the value returned by toJSON.

What JSON cannot hold

RECORDED FACTS

JSON is excellent for simple data, but it is lossy outside that lane. Here are the edge cases recorded from real JavaScript behavior.

Values that change, disappear, or throw during JSON.stringify
ValueExample outputRound tripWhy
Object property undefined{"a":1}b is goneObject properties whose value is undefined, a function, or a symbol are omitted.
Array slot undefined[1,null]undefined becomes nullArrays keep their length, so unsupported entries become null.
NaN and infinities{"n":null,"inf":null}Numbers become nullJSON has no spelling for not-a-number or infinity.
Date{"d":"2024-01-01T00:00:00.000Z"}Date becomes stringDate.prototype.toJSON returns the ISO string before stringify writes it.
Map and Set{"m":{},"s":{}}Entries are lostTheir data is not stored in enumerable string-keyed properties.
BigIntTypeErrorNothing is producedBigInt must be converted deliberately, usually to a string.
Circular referenceTypeErrorNothing is producedJSON is a tree. An object pointing back to itself is a graph.

Object property undefined: {"a":1}

Array slot undefined: [1,null]

NaN and infinities: {"n":null,"inf":null}

Date: {"d":"2024-01-01T00:00:00.000Z"}

Map and Set: {"m":{},"s":{}}

BigInt: TypeError

Circular reference: TypeError

Survives a JSON round trip?
  • "hello"
  • 42
  • { user: { name: "Ava" } }
  • new Date('2024-01-01T00:00:00.000Z')
  • { name: undefined }
  • { save() {} }
  • { score: NaN }
  • new Map([["a", 1]])
  • [1, undefined]
  • { id: 1n }
Try it yourself
0 of 10 correct

Sort each value by what happens after JSON.stringify followed by JSON.parse.

Choose a category for every card. You can change an answer at any time; Reset clears them all.

This is why a JSON round trip is only a limited deep-copy trick. It can copy plain data, but structuredClone is the more honest tool for many JavaScript values. The BigInt lesson explains why 1n is not the same type as 1, and Numbers explains why huge numeric IDs can lose precision.

Source text access and JSON.rawJSON

RECENT

Recent engines add two tools for advanced JSON work. First, a reviver can receive a third context argument for primitive values. context.source is the exact JSON source text for that value. That matters for huge numbers because JSON.parse normally converts them to Number before your code sees them.

Big IDs without losing digits
Revive a big ID from source textPop out in the code editor (opens in a new tab)JavaScript
const text = '{"id": 12345678901234567890}'; const rounded = JSON.parse(text).id;const safe = JSON.parse(text, (key, value, context) => {  if (key === "id") return BigInt(context.source);  return value;}).id; console.log(rounded);console.log(safe);
Real outputchecking
JSON.parse numberchecking
context.source BigIntchecking
JSON.rawJSON outputchecking support after mount
Try it yourself
Feature detect: context.source unavailable; JSON.rawJSON unavailable

Recent JavaScript engines can expose the original source text to a reviver and can insert trusted raw JSON. Feature-detect before using these in browsers.

Node 22 supports these APIs. Older browsers may need a string fallback for big IDs.

In Node 22, JSON.parse('{"id": 12345678901234567890}').id is the rounded number 12345678901234567000. With context.source, the reviver can read the original digits and create 12345678901234567890n. Second, JSON.rawJSON("12345678901234567890") creates a trusted raw JSON value so JSON.stringify({ id: JSON.rawJSON("12345678901234567890") }) outputs a raw number token.

Raw JSON output when supportedPop out in the code editor (opens in a new tab)JavaScript
const id = JSON.rawJSON("12345678901234567890");const text = JSON.stringify({ id });console.log(text);

Feature-detect in browsers after mount with typeof JSON.rawJSON === "function", JSON.isRawJSON, and a small parse test for context.source. These additions are useful, but strings remain the simplest cross-platform way to move big IDs safely.

Where you will use this

JSON appears anywhere data crosses a boundary: saving settings in storage, putting data into a file, sending request bodies, reading API responses, logging safe snapshots, and writing tests with fixture data. Stage 7’s Fetch & JSON lesson will combine fetch with response.json(); the JSON rules here are the same rules underneath.

Common JSON jobs
JobToolWatch out for
Save simple preferencesJSON.stringify(settings)Only store data you can rebuild later.
Read configuration textJSON.parse(text)Handle SyntaxError and show a friendly message.
Remove secrets before loggingReplacer functionReturn undefined for object properties you do not want.
Restore known richer valuesReviver functionValidate strings before turning them into Dates or BigInts.

Common misconceptions

  • “JSON is any JavaScript object text.” No. JSON has stricter grammar: double quotes, no comments, no trailing commas.
  • “JSON.parse restores the original objects.” It creates fresh plain objects and arrays. Prototypes and methods are gone unless you revive or rebuild them.
  • “Dates survive JSON.” They become strings through Date.prototype.toJSON.
  • “Map serializes like an object.” A Map’s entries are not enumerable string-keyed properties, so it becomes {} unless you convert it.
  • “Big numbers are always safe in JSON.” JSON text may contain many digits, but JavaScript Number precision is limited. Use strings or context.source plus BigInt when available.
  • “A replacer and reviver run at the same time.” Replacer runs while stringifying; reviver runs later while parsing.
Replacer vs reviver
HookUsed byMain jobCan remove?
ReplacerJSON.stringify(value, replacer)Change or filter values before JSON text is producedYes: return undefined for object properties
ReviverJSON.parse(text, reviver)Change parsed values before the final result is returnedYes: return undefined to delete a property from its holder

Practice exercises

5 EXERCISES
Exercise 1 · Warm-upPredict stringify output

Without running it first, type the exact string printed by the program.

Starter codePop out in the code editor (opens in a new tab)JavaScript
const data = {
  name: "Ava",
  score: NaN,
  skip: undefined,
  tags: ["js", undefined],
};
console.log(JSON.stringify(data));

Answer, then press Check. Spacing and letter case don’t matter.

    Exercise 2 · PracticeStrip passwords with a replacer

    Write a replacer that removes every property named password, then check the JSON text.

    Starter codePop out in the code editor (opens in a new tab)JavaScript
    const user = { name: "Ava", password: "secret", nested: { password: "hidden" } };
    const text = JSON.stringify(user, (key, value) => {
      if (key === "password") return undefined;
      return value;
    });
    console.log(text);

    Answer, then press Check. Spacing and letter case don’t matter.

      Exercise 3 · PracticeRevive a date

      Parse the event so starts becomes a Date again.

      Starter codePop out in the code editor (opens in a new tab)JavaScript
      const event = JSON.parse('{"starts":"2024-01-01T00:00:00.000Z"}', (key, value) => {
        if (key === "starts") return new Date(value);
        return value;
      });
      console.log(event.starts instanceof Date);
      console.log(event.starts.toISOString());

      Answer, then press Check. Spacing and letter case don’t matter.

        Exercise 4 · PracticeMake a class serialize with toJSON

        Add packing instructions to a class-like value so JSON text is friendly.

        Starter codePop out in the code editor (opens in a new tab)JavaScript
        class Money {
          constructor(cents, currency) {
            this.cents = cents;
            this.currency = currency;
          }
          toJSON() {
            return (this.cents / 100).toFixed(2) + " " + this.currency;
          }
        }
        console.log(JSON.stringify({ price: new Money(1250, "USD") }));

        Answer, then press Check. Spacing and letter case don’t matter.

          Exercise 5 · ChallengeParse a big ID safely

          Use context.source to avoid Number precision loss.

          Starter codePop out in the code editor (opens in a new tab)JavaScript
          const text = '{"id": 12345678901234567890}';
          const safe = JSON.parse(text, (key, value, context) => {
            if (key === "id") return BigInt(context.source);
            return value;
          });
          console.log(safe.id);

          Answer, then press Check. Spacing and letter case don’t matter.

            Check your understanding

            8 QUESTIONS
            JSON quiz · 8 questionsScore: first tries count
            1. Question 1 of 8What does JSON.stringify do?

              Choose an answer to see the explanation.

            2. Question 2 of 8What does output question 1 print?

              Read the code, then predictPop out in the code editor (opens in a new tab)JavaScript
              console.log(JSON.stringify({ a: 1, b: undefined, c: null }));

              Choose an answer to see the explanation.

            3. Question 3 of 8What does output question 2 print?

              Read the code, then predictPop out in the code editor (opens in a new tab)JavaScript
              console.log(JSON.stringify([1, undefined, NaN, Infinity]));

              Choose an answer to see the explanation.

            4. Question 4 of 8Why might you pass a replacer function to JSON.stringify?

              Choose an answer to see the explanation.

            5. Question 5 of 8What does output question 3 print?

              Read the code, then predictPop out in the code editor (opens in a new tab)JavaScript
              const obj = JSON.parse('{"d":"2024-01-01T00:00:00.000Z"}');
              console.log(obj.d instanceof Date);

              Choose an answer to see the explanation.

            6. Question 6 of 8What does output question 4 print?

              Read the code, then predictPop out in the code editor (opens in a new tab)JavaScript
              const item = { value: 7, toJSON() { return "seven"; } };
              console.log(JSON.stringify({ item }));

              Choose an answer to see the explanation.

            7. Question 7 of 8Which text is invalid JSON?

              Choose an answer to see the explanation.

            8. Question 8 of 8What problem can reviver context.source solve?

              Choose an answer to see the explanation.

            Key takeaways

            • JSON.stringify packs supported data into text; JSON.parse unpacks valid JSON text.
            • Replacers filter or transform before shipping; revivers rebuild selected values while receiving.
            • toJSON lets an object choose its own JSON-friendly representation.
            • JSON cannot hold functions, undefined, symbols, BigInt, circular references, object identity, prototypes, or Date objects as Dates.
            • Recent context.source and JSON.rawJSON APIs help with exact source text, but feature-detect them.

            Final definition: JSON is a strict, portable text format for simple data, with hooks for carefully changing what leaves and what comes back.

            Up next: Choosing a data structure.

            CompleteFrontend Clear concepts. Working examples.