WasiLearn Code
JavaScript · Arrays and objects

JSON: objects that travel

An object lives in your program's memory. To send it to a server, save it in a file, or put it in local storage, it has to become text — because text is the only thing that survives the journey.

JSON is that text format, and JavaScript has exactly two functions for it.

const student = { name: "Ayesha", marks: 72 };

const text = JSON.stringify(student);
console.log(text);
console.log(typeof text);
output
{"name":"Ayesha","marks":72}
string

That looks almost identical to the object and is a completely different kind of thing. It is a string. You cannot read text.name from it.

stringify goes out, parse comes back

const student = { name: "Ayesha", marks: 72 };

const text = JSON.stringify(student);
const back = JSON.parse(text);

console.log(back.name);
console.log(back.marks + 1);
console.log(typeof back);
output
Ayesha
73
object

JSON.parse turns the text back into a real object with working properties. Those two functions are the entire API, and almost every mistake with JSON is forgetting which side of them you are on.

const text = '{"name":"Ayesha"}';

console.log(text.name);
console.log(JSON.parse(text).name);
output
undefined
Ayesha

text.name is undefined because a string has no name property. Whenever a property reads as undefined on data that arrived from somewhere, check whether you parsed it.

JSON is stricter than JavaScript

JSON looks like JavaScript object syntax and is a narrower language.

console.log(JSON.stringify({ name: "Ayesha", ok: true, note: null }));
output
{"name":"Ayesha","ok":true,"note":null}

The rules:

  • Keys must be double-quoted strings. {name: "x"} is valid JavaScript and invalid JSON.
  • Strings use double quotes only. Single quotes are not allowed.
  • No trailing commas. Legal in modern JavaScript, never in JSON.
  • No comments. There is no way to write one.
  • Values may be a string, number, boolean, null, array or object. Nothing else.
try {
  JSON.parse("{'name': 'Ayesha'}");
} catch (error) {
  console.log(error.name);
}
output
SyntaxError

Single quotes, so it is not JSON. JSON.parse throws rather than guessing, which is the right behaviour and the reason for the section below on catching it.

What stringify silently drops

This is the part that produces confusing bugs, because nothing warns you.

const record = {
  name: "Ayesha",
  greet() { return "hi"; },
  missing: undefined,
  when: new Date("2024-03-01T10:00:00Z"),
  big: 5,
};

console.log(JSON.stringify(record));
output
{"name":"Ayesha","when":"2024-03-01T10:00:00.000Z","big":5}

The function is gone. The undefined property is gone — not set to null, gone, the key is absent entirely. And the Date has quietly become a string, so after a round trip it is no longer a date:

const back = JSON.parse(JSON.stringify({ when: new Date("2024-03-01") }));

console.log(typeof back.when);
console.log(back.when instanceof Date);
output
string
false

Calling back.when.getFullYear() throws. If dates matter, convert them back explicitly after parsing — new Date(back.when).

In arrays the treatment differs again: undefined becomes null rather than disappearing, because dropping it would shift every later index.

console.log(JSON.stringify([1, undefined, 3]));
console.log(JSON.stringify({ a: 1, b: undefined, c: 3 }));
output
[1,null,3]
{"a":1,"c":3}

Same value, two different fates, decided by whether it sat in an array or an object.

Formatting for humans

JSON.stringify takes two more arguments. The third is indentation.

const student = { name: "Ayesha", marks: { maths: 72 } };

console.log(JSON.stringify(student, null, 2));
output
{
  "name": "Ayesha",
  "marks": {
    "maths": 72
  }
}

That is the standard way to write a config file or print something readable while debugging. The null in the middle is the second argument, which can filter properties:

const student = { name: "Ayesha", marks: 72, secret: "x" };

console.log(JSON.stringify(student, ["name", "marks"]));
output
{"name":"Ayesha","marks":72}

Passing an array of keys keeps only those — a quick way to avoid sending fields that should not leave the server.

Nesting works, cycles do not

const order = {
  customer: { name: "Bilal" },
  items: [{ name: "chai", qty: 2 }],
};

const back = JSON.parse(JSON.stringify(order));

console.log(back.customer.name);
console.log(back.items[0].qty);
output
Bilal
2

Any depth of objects and arrays round-trips fine. What does not is an object that refers back to itself:

const a = { name: "a" };
a.self = a;

try {
  JSON.stringify(a);
} catch (error) {
  console.log(error.name);
}
output
TypeError

TypeError: Converting circular structure to JSON. It cannot be represented as text, so there is nothing sensible to produce. It usually means you attached a parent reference somewhere and can drop it before stringifying.

Always catch parse

Data from a server, a file or local storage is not guaranteed to be valid JSON. A failed parse throws, and an unhandled throw stops everything.

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

console.log(safeParse('{"name":"Ayesha"}'));
console.log(safeParse("not json"));
console.log(safeParse("not json", {}));
output
{ name: 'Ayesha' }
null
{}

catch with no binding is allowed when you do not need the error object.

The commonest real cause of a parse failure is not corrupt data — it is an error page. A server that returns HTML saying "500 Internal Server Error" makes JSON.parse fail on the very first character, which is why the message so often reads Unexpected token '<'. That < is the start of <!DOCTYPE html>, and the actual problem is on the server.

The deep-copy trick, and why not to use it

const original = { name: "Ayesha", marks: { maths: 72 } };
const copy = JSON.parse(JSON.stringify(original));

copy.marks.maths = 100;
console.log(original.marks.maths);
output
72

It works — the nested object really is independent, unlike a spread copy. But it silently destroys everything listed above: dates become strings, functions and undefined vanish, and it throws on a cycle.

structuredClone(original) does the same job properly and is built in. Use that.

Storing and sending

const settings = { theme: "dark", fontSize: 16 };

const stored = JSON.stringify(settings);
const loaded = JSON.parse(stored);

console.log(loaded.theme);
output
dark

localStorage holds strings only, so this stringify-and-parse pair is exactly what you write around it. When fetching from a server, response.json() does the parsing for you — it is JSON.parse with the network attached, and it throws in the same way when the response is not JSON.

The two rules

  1. JSON is text. Parse it before reading properties; stringify before sending or storing.
  2. Assume the parse can fail, because the failure is usually somebody else's server having a bad day.

That closes the arrays and objects section. If a property is coming back undefined, the objects lesson covers the access rules, and destructuring covers pulling the parsed data apart.

Prefer learning with a teacher?

WasiLearn Academy runs small-group classes covering this material, online and in Karachi.

See the classes