Module 4 · Objects, Classes & Data Structures ⏱ 19 min

JSON Serialization & Parsing

By the end of this lesson you will be able to:
  • Convert JavaScript objects to JSON text with JSON.stringify and back with JSON.parse
  • Predict which values survive a JSON round trip and which are silently dropped
  • Guard against circular references and avoid treating JSON as a deep clone

Your objects live in memory, alive and full of behaviour — but the moment they need to leave your program, whether down a network cable to an API or into a file for later, they have to become text. The text format the web agreed on is JSON (JavaScript Object Notation), and it is deliberately tiny: just strings, numbers, booleans, null, arrays, and plain objects built from those.

JavaScript gives you two functions to cross that boundary. JSON.stringify turns a value into a JSON string, and JSON.parse turns that string back into a fresh value. Together they are how you send a shopping cart to a server, save a user's settings, or cache a result. The catch — and there is one — is that JSON is a strict subset of JavaScript, so not everything in your object makes it across.

flowchart LR
  O["object<br/>{ name: #quot;Ada#quot;, age: 36 }"] -->|"JSON.stringify"| S["string<br/>{#quot;name#quot;:#quot;Ada#quot;,#quot;age#quot;:36}"]
  S -->|"JSON.parse"| O2["object (a fresh copy)"]
  style S fill:#e0900b,color:#fff
  style O2 fill:#1e7f3d,color:#fff
stringify turns an object into JSON text; parse turns the text back into a fresh object.

The two directions

Serialising is a single call. JSON.stringify(person) returns a string — note the quotes inside it, because the whole object is now text. Parsing reverses it: JSON.parse(text) reads that string and hands back a brand-new object with the same data. Every call to parse builds a separate object, so two parses of the same text give two independent objects that merely hold equal values.

A useful extra: stringify takes a third argument for pretty-printing. Pass null (no replacer) and a number, and the output gains newlines and indentation so a human can read it — JSON.stringify(obj, null, 2). The compact form is for the wire; the indented form is for log files and debugging.

Object to string and back again. The string carries the data; parse rebuilds a fresh object. Press Run.
const person = { name: "Ada", age: 36, skills: ["js", "sql"], active: true };

const text = JSON.stringify(person);
console.log(text);
console.log(typeof text);

const back = JSON.parse(text);
console.log(back.name);
console.log(back.skills[1]);

What survives, what gets dropped

Because JSON is a subset, stringify has to leave things behind. Numbers, strings, booleans, null, arrays, and plain objects all cross safely. Functions, undefined, and Symbol keys or values do not — they vanish silently, with no error and no warning. An object can quietly shrink as it serialises, which is the first surprise.

The second is type reshaping: a Date has no JSON representation, so it is converted to its ISO string, and NaN or Infinity become null. After a round trip, what was a Date object is now a string, so typeof back.created is "string", not "object". Always assume a value that left the JSON boundary may have changed type on the way back.

flowchart TD
  K["survives: numbers, strings, booleans, arrays, plain objects, null"] --> G["stays in JSON"]
  D["dropped: functions, undefined, Symbol"] --> X["vanishes silently"]
  style G fill:#1e7f3d,color:#fff
  style X fill:#b91c1c,color:#fff
Primitives and plain structures survive; functions, undefined, and Symbols vanish without an error.
undefined and the function are gone after stringify; the Date became a string.
const mixed = {
  count: 3,
  name: "Kit",
  skip: undefined,       // dropped
  run: function () {},   // dropped
  created: new Date(0)   // becomes an ISO string
};

const text = JSON.stringify(mixed);
console.log(text);

const back = JSON.parse(text);
console.log(back.skip);
console.log(typeof back.run);
console.log(typeof back.created);
flowchart TD
  N["node.self = node"] -->|"JSON.stringify"| T["TypeError: circular structure"]
  T --> F["guard with try/catch"]
  style T fill:#b91c1c,color:#fff
  style F fill:#e0900b,color:#fff
A self-referential object makes stringify throw; guard it or break the loop first.
Pretty-print with an indent, and catch the circular-structure error instead of crashing.
const cfg = { host: "localhost", port: 8080, flags: { debug: true } };

console.log(JSON.stringify(cfg, null, 2));   // pretty, 2-space indent

const node = { name: "loop" };
node.self = node;                            // now circular
let result = "ok";
try {
  JSON.stringify(node);
} catch (e) {
  result = "circular caught";
}
console.log(result);
Exercise

What does this print? JSON.stringify returns compact text with quoted keys.

const obj = { a: 1, b: [2, 3] };
console.log(JSON.stringify(obj));
Exercise

Write totalScores(jsonText) that takes a JSON string holding an array of numbers, parses it with JSON.parse, and returns the sum. So totalScores('[10, 20, 5]') returns 35.

function totalScores(jsonText) {
  // parse the JSON text and return the sum of the array
}
Exercise

This object has a greet method, and the code round-trips it through JSON expecting back.greet() to still work — but JSON.stringify drops functions, so after the round trip greet is gone and the call throws TypeError. Fix buildGreeting so it returns the greeting after a round trip: compute the greeting string before serialising (store it as a plain string on the object), so nothing depends on a method surviving JSON.

function buildGreeting(person) {
  const original = {
    name: person,
    greet() { return `Hello, ${this.name}`; }
  };
  const text = JSON.stringify(original);
  const back = JSON.parse(text);
  return back.greet();   // BUG: greet was dropped by JSON.stringify
}
Exercise

What does this print? Nested arrays and objects survive a round trip intact.

const data = { users: [{ name: "Ada" }, { name: "Grace" }] };
const text = JSON.stringify(data);
const back = JSON.parse(text);
console.log(back.users.length);
console.log(back.users[0].name);
console.log(typeof text);
Exercise

You run JSON.stringify({ a: 1, b: undefined, c: function(){} }). What is in the resulting string?

Recap

  • JSON.stringify(value) turns a value into compact JSON text; JSON.parse(text) turns it back into a fresh object.
  • Pass a third argument (null, 2) to pretty-print for logs and debugging.
  • Surviving types: numbers, strings, booleans, null, arrays, plain objects. Dropped: functions, undefined, Symbol. Reshaped: Date → ISO string, NaN/Infinitynull.
  • A circular reference makes stringify throw — break the loop or wrap it in try/catch.
  • A JSON round trip is not a clone: it loses methods and edge-case values. Use it for transport and storage; use structuredClone for an exact copy.

That completes the module. You can now model data with objects and classes, share behaviour through prototypes, pick the right collection for the job, pull values apart with destructuring, and move it all across the network as JSON.

Checkpoint quiz

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

What happens when you call JSON.stringify on an object that contains itself?

Go deeper — technical resources