JSON

JavaScript · Lesson 46 of 61 7 min read Updated

When a browser talks to a server, when you save a cart in localStorage, or when two programs written in different languages share data, the data usually travels as JSON. It is plain text that any language can read and write.

In this lesson you will learn the JSON rules, turn JavaScript values into JSON with JSON.stringify(), turn JSON back into values with JSON.parse(), control both directions with replacers, revivers and toJSON(), and see the new ES2026 way to keep very large numbers exact.

What JSON looks like

JSON stands for JavaScript Object Notation. It borrowed its look from JavaScript objects and arrays, but it is a stricter, text-only format:

JSON
{
  "name": "Meera",
  "city": "Jaipur",
  "age": 21,
  "scholarship": true,
  "guardian": null,
  "subjects": ["Physics", "Chemistry", "Maths"],
  "address": { "pin": "302001", "state": "Rajasthan" }
}

The rules are short but strict:

  • Keys must be in double quotes. Strings use double quotes too, never single quotes.
  • Values can only be strings, numbers, true, false, null, objects and arrays.
  • No undefined, no functions, no dates, no NaN or Infinity.
  • No comments and no trailing comma after the last item.

A single mistake, such as one trailing comma, makes the whole text invalid.

JSON.stringify: value to text

JSON.stringify(value) turns a value into a JSON string. Pay attention to what happens to values that JSON cannot represent:

JavaScriptRun it yourself
const order = {
  id: 5012,
  customer: "Ananya",
  items: ["Paneer roll", "Lassi"],
  total: 240,
  paid: true,
  coupon: null,
  note: undefined,
  placedAt: new Date(Date.UTC(2026, 9, 29, 6, 30)),
  describe() {
    return `${this.customer}: ₹${this.total}`;
  },
};

console.log(JSON.stringify(order));
console.log(JSON.stringify([undefined, NaN, Infinity]));
console.log(JSON.stringify(new Map([["chai", 20]])));
console.log(JSON.stringify({ city: "Kochi", pin: 682001 }, null, 2));
Output
{"id":5012,"customer":"Ananya","items":["Paneer roll","Lassi"],"total":240,"paid":true,"coupon":null,"placedAt":"2026-10-29T06:30:00.000Z"}
[null,null,null]
{}
{
  "city": "Kochi",
  "pin": 682001
}

Notice four things:

  • The note property (undefined) and the describe method disappeared completely.
  • The date became a string in ISO format. It will not turn back into a date by itself.
  • Inside arrays, undefined, NaN and Infinity become null.
  • A Map (and a Set) becomes an empty object, so convert it first, for example with Object.fromEntries(map) or [...set].

The third argument adds indentation. JSON.stringify(value, null, 2) gives readable output with two spaces per level, which is handy for logs and config files.

Choosing what to save: replacers and toJSON

The second argument of JSON.stringify() is a replacer. An array replacer lists the keys to keep. A function replacer is called for every key and value, and whatever it returns is saved instead; returning undefined leaves the key out. An object can also have a toJSON() method, which decides how that object is saved. That is exactly how dates become strings.

JavaScriptRun it yourself
const user = {
  name: "Kabir",
  email: "kabir@example.com",
  password: "chai@123",
  balance: 1520.4567,
};

// 1. An array replacer keeps only these keys
console.log(JSON.stringify(user, ["name", "email"]));

// 2. A function replacer sees every key and value
const safe = JSON.stringify(user, (key, value) => {
  if (key === "password") return undefined;               // leave it out
  if (typeof value === "number") return Number(value.toFixed(2));
  return value;
});
console.log(safe);

// 3. toJSON lets an object decide how it is saved
class Money {
  constructor(paise) {
    this.paise = paise;
  }
  toJSON() {
    return `₹${(this.paise / 100).toFixed(2)}`;
  }
}
console.log(JSON.stringify({ fee: new Money(129900) }));
Output
{"name":"Kabir","email":"kabir@example.com"}
{"name":"Kabir","email":"kabir@example.com","balance":1520.46}
{"fee":"₹1299.00"}

JSON.parse: text to value

JSON.parse(text) reads a JSON string and builds the matching JavaScript value. If the text breaks any rule, it throws a SyntaxError, so wrap it in try...catch whenever the text comes from outside your code. The optional second argument is a reviver: a function called for every key and value, which can return a replacement. It is the natural place to turn date strings back into Date objects.

JavaScriptRun it yourself
const text = '{"name":"Rohan","marks":[78,85,91],"passed":true,"joined":"2025-06-02T04:30:00.000Z"}';

const student = JSON.parse(text);
console.log(student.marks[1] + 5);
console.log(typeof student.joined);

// A reviver runs for every key and can change the value
const revived = JSON.parse(text, (key, value) => {
  return key === "joined" ? new Date(value) : value;
});
console.log(revived.joined instanceof Date, revived.joined.getUTCFullYear());

// Invalid JSON throws a SyntaxError
const attempts = ["{'name': 'Rohan'}", '{"name": "Rohan",}', ""];
for (const attempt of attempts) {
  try {
    JSON.parse(attempt);
  } catch (error) {
    console.log(`${error.name}: ${attempt || "(empty string)"}`);
  }
}
Output
90
string
true 2025
SyntaxError: {'name': 'Rohan'}
SyntaxError: {"name": "Rohan",}
SyntaxError: (empty string)

The parsed marks are real numbers, so 85 + 5 gives 90. Single quotes, a trailing comma and even an empty string are all invalid JSON. The empty string catches many people: a server that replies with an empty body makes JSON.parse("") throw.

You already used these two functions in localStorage & sessionStorage, and response.json() from the Fetch API is simply JSON.parse() applied to the response body.

Copying objects: structuredClone, not JSON

You may see JSON.parse(JSON.stringify(obj)) used to make a deep copy of an object. It works only for plain data and silently breaks anything else. The built-in structuredClone() is the better choice today:

JavaScriptRun it yourself
const original = {
  name: "Isha",
  joined: new Date(Date.UTC(2025, 0, 15)),
  courses: new Set(["html", "js"]),
};

const viaJson = JSON.parse(JSON.stringify(original));
const viaClone = structuredClone(original);

console.log(typeof viaJson.joined, viaJson.courses);
console.log(viaClone.joined instanceof Date, viaClone.courses.has("js"));
Output
string {}
true true

The JSON copy turned the date into a string and emptied the set. structuredClone() kept both. Neither can copy functions or class methods.

Common mistakes

  • Parsing something that is already an object. JSON.parse({ a: 1 }) turns the object into the text "[object Object]" and throws a SyntaxError.
  • Stringifying twice. JSON.stringify(JSON.stringify(data)) gives a string full of escaped quotes. Convert once.
  • Writing JSON by hand with single quotes or trailing commas. Build the object in JavaScript and let JSON.stringify() write the text.
  • Expecting dates to come back as dates. Use a reviver, or store a timestamp number.
  • Circular references. An object that refers to itself throws TypeError: Converting circular structure to JSON.

Key points

  • JSON is a strict text format: double-quoted keys and strings, no comments, no trailing commas, no undefined or functions.
  • JSON.stringify() drops undefined and functions, turns dates into strings, and indents output with its third argument.
  • Replacers and toJSON() control what gets saved; revivers control what comes back.
  • JSON.parse() throws a SyntaxError for invalid text, so wrap it in try...catch.
  • Use structuredClone() for deep copies instead of a JSON round trip.
  • ES2026 gives revivers the original source text, so large numbers can be kept exact.

Exercise

Create an object for a cricket match with teams (an array of two names), venue, date (a Date) and a secretScorerPin. Convert it to pretty JSON with two-space indentation, leaving out secretScorerPin. Then parse the text back with a reviver so that date becomes a real Date again, and log the year using getUTCFullYear().

Show solution
JavaScript
const match = {
  teams: ["Rohtak Royals", "Jaipur Jaguars"],
  venue: "Lucknow",
  date: new Date(Date.UTC(2026, 10, 14)),
  secretScorerPin: "4471",
};

const json = JSON.stringify(
  match,
  (key, value) => (key === "secretScorerPin" ? undefined : value),
  2
);
console.log(json);

const back = JSON.parse(json, (key, value) => (key === "date" ? new Date(value) : value));
console.log(back.date.getUTCFullYear()); // 2026

The replacer returns undefined for the PIN, so that key is left out. The reviver rebuilds the date from its ISO string.

Quick Quiz

1. Which of these is valid JSON?

2. What does JSON.stringify({ a: 1, b: undefined }) return?

3. What happens when JSON.parse() receives invalid text?

4. What is the best way to deep-copy an object that contains a Date and a Set?

Learn JavaScript with a mentor

Free lessons get you started. Our Full Stack Web Development training adds real projects and code reviews from working developers.

  • Project-based learning
  • Mentor code reviews
  • Free counselling call