JSON, parse and stringify
Everything in this module has been objects and arrays living inside one running
programme. The moment data has to leave — to a server, to localStorage, to a
file — it has to become text. JSON is how.
What JSON is
JavaScript Object Notation: a text format that looks like JavaScript object syntax and is not JavaScript. It is a data format, and every language reads it — the Django backend an intern meets next, an Android app, a payment gateway.
{
"customer": "Priya",
"plates": 3,
"paid": true,
"items": ["dal", "rice"],
"address": { "city": "Pune" }
}
The rules are stricter than JavaScript's, and the differences are exactly where people get stuck:
| JSON requires | JavaScript allows |
|---|---|
| Double quotes on keys | Unquoted keys |
| Double quotes on strings | Single quotes, backticks |
| No trailing comma | Trailing commas |
| No comments | // comments |
Only string, number, boolean, null, array, object |
Functions, undefined, Date, Map |
That last row causes the interesting problems.
Out: stringify
const order = { customer: 'Priya', plates: 3, paid: true };
console.log(JSON.stringify(order));
{"customer":"Priya","plates":3,"paid":true}
A string, ready to send. For something readable, pass a third argument:
console.log(JSON.stringify({ a: 1, b: [1, 2] }, null, 2));
{
"a": 1,
"b": [
1,
2
]
}
The 2 is the indent; the null is a filter you will rarely use. Use the
indented form for logs and files, the compact one for the network — every
newline is bytes over somebody's mobile data.
In: parse
const text = '{"customer":"Priya","plates":3}';
const order = JSON.parse(text);
console.log(order.plates + 1);
4
Back to a real object, with real numbers.
JSON.parse throws on bad input, which matters because bad input is normal —
a truncated response, an HTML error page where you expected JSON:
JSON.parse('{plates:3}');
SyntaxError: Expected property name or '}' in JSON at position 1
JSON.parse(undefined);
SyntaxError: "undefined" is not valid JSON
That second one is worth recognising: it means you parsed something that was not
there at all — usually a response that failed. Wrap JSON.parse in
try/catch whenever the input came from outside your programme. Module 5
covers try/catch properly; module 8 uses it on every response.
What gets lost
This is the part that produces real bugs, because it fails silently.
const data = {
customer: 'Priya',
plates: 3,
note: undefined,
when: new Date('2026-09-27T10:00:00Z'),
greet() {
return 'hi';
},
};
console.log(JSON.stringify(data));
{"customer":"Priya","plates":3,"when":"2026-09-27T10:00:00.000Z"}
Three things happened, none of them announced:
undefinedvanished. The key is gone entirely — notnull, absent.- The method vanished. Functions are not data.
- The
Datebecame a string. It looks fine, and it is no longer a date.
That last one is the trap. A round trip does not give you back what you put in:
const back = JSON.parse(JSON.stringify({ when: new Date() }));
console.log(typeof back.when);
console.log(back.when.getFullYear);
string
undefined
getFullYear is undefined, so calling it throws back.when.getFullYear is not a function. Dates do not survive JSON. You must convert them back yourself:
new Date(back.when). Module 6 returns to this.
Three more surprises worth knowing:
console.log(JSON.stringify([1, undefined, 3]));
console.log(JSON.stringify({ a: NaN, b: Infinity }));
[1,null,3]
{"a":null,"b":null}
In an array, undefined becomes null rather than disappearing — because
dropping it would shift every later index. And NaN and Infinity both become
null, since JSON has no way to write them. A failed calculation arrives at
the server as an innocent-looking null.
And a circular reference is a hard error:
const order = { customer: 'Priya' };
order.self = order;
JSON.stringify(order);
TypeError: Converting circular structure to JSON
Rare in plain data, common the moment you try to stringify something from the DOM — every element refers to its parent, which refers back.
| Value going in | Comes out as |
|---|---|
undefined in an object |
key removed |
undefined in an array |
null |
| Function | removed |
Date |
ISO string |
NaN, Infinity |
null |
Map, Set |
{} |
| Circular reference | TypeError |
Map and Set becoming {} is the row that catches people — no error, no
warning, just an empty object where your data was. Convert with
Object.fromEntries(map) or [...set] before stringifying.
The deep-copy trick, and why not to use it
You will see this:
const copy = JSON.parse(JSON.stringify(original));
It does produce a genuine deep copy, and before structuredClone existed it was
the standard move. But it silently destroys everything in the table above. Use
structuredClone(original) instead — it keeps dates, handles circular
references, and is built in.
Check your work
JSON needs double quotes on keys and strings, and allows no trailing commas or comments. Single quotes are the commonest reason hand-written JSON fails to parse.
JSON.stringify(order) gives a string; the third argument indents it.
Indent for humans, compact for the network.
JSON.parse('{plates:3}') throws SyntaxError because the key is not
quoted. JSON.parse(undefined) gives SyntaxError: "undefined" is not valid JSON, which usually means the thing you were parsing never arrived.
Stringifying drops undefined properties and functions, and turns a Date
into a string. None of this warns you.
After a round trip, typeof back.when is 'string' and back.when .getFullYear is undefined. Rebuild with new Date(back.when).
JSON.stringify([1, undefined, 3]) is [1,null,3] — in an array,
undefined becomes null so the indices still line up.
NaN and Infinity both stringify to null.
A Map stringifies to {} with no error at all.
A circular reference throws TypeError: Converting circular structure to JSON.
JSON.parse(JSON.stringify(x)) deep-copies but destroys dates, Maps and
anything else in the table. structuredClone(x) is the modern answer.
save and load:
function load(store, key) {
const text = store[key];
if (text === undefined) return null;
try {
const data = JSON.parse(text);
if (typeof data?.when === 'string') data.when = new Date(data.when);
return data;
} catch {
return null;
}
}
Three separate failures, three answers. A key that was never saved is undefined
before parsing ever happens, so check it first rather than letting
JSON.parse(undefined) throw. Corrupted text throws, and the catch turns it
into null. And the date is rebuilt by hand, because nothing else will do it for
you — typeof data?.when === 'string' guards against the stored object having no
when at all.
Returning null for both failures is a deliberate simplification. In real code
you often want to tell "never saved" apart from "saved but corrupted", because
the second one means something is wrong and deserves a log.
Practice
- Stringify an order object, then parse it back and do arithmetic on a number from it.
- Print the indented and the compact form of the same object side by side.
- Break the parser three ways: single quotes, a trailing comma, and an unquoted key. Read each error.
- Parse
undefinedand recognise the message. Then wrap the call so a bad response does not crash your programme. - Run the lossy example. Stringify an object containing
undefined, a function and aDate, and account for all three results. - Round-trip a
Dateand then call.getFullYear()on it. Read the error, then fix it withnew Date(...). - Stringify
[1, undefined, 3]and{ a: NaN }and explain bothnulls. - Stringify a
Mapwith entries in it, get{}, and then do it properly withObject.fromEntries. - Build a circular reference and read the
TypeError. - Harder. Write
save(key, value)andload(key)that round-trip an object containing aDatethrough a string and back, with the date arriving as a realDate. Then handleloadbeing given a key that was never saved, and a key whose stored text has been corrupted. Both must return something sensible rather than throwing — this is exactly what module 7's storage lesson will ask of you.
That is module four. You can now shape data the way real applications do —
objects for records, arrays for lists, map/filter/reduce instead of loops,
destructuring to take it apart, ?. and ?? to survive missing fields, Map
and Set for the jobs objects do badly, and JSON to send it anywhere.
One idea has run through the whole module and is worth stating on its own: objects and arrays are handled by reference, and copying them is shallow by default. Almost every confusing bug in this module was a version of that.
Next module: classes and errors — building your own types, and failing on purpose instead of by accident.
Stuck on this lesson?
Being stuck is part of it — but being stuck alone for three days is not. Our internship programme pairs this curriculum with code review and one-to-one help from working developers, and it is free.
About the internship