Skip to content
aviral gupta

// B5.4 · ~30 min · Beginner

JSON: parse and stringify

After this lesson you turn data into JSON text and back, write JSON that parses, and check what you loaded.

Lesson 4 of 5 in B5 Arrays, objects and JSON

You will be able to

  • Tell JSON text from objects and write valid JSON: double quotes, no trailing commas, no comments, no undefined
  • Parse JSON text, read its SyntaxError, and check the result with typeof and Array.isArray
  • Write JSON with JSON.stringify and an indent, know what it drops, and copy plain data with a round trip
  1. Warm-up · Activity 1 of 7

    Warm-up from lesson B5.3: what does this print?

    const item = { name: "tea", price: 3 };
    const copy = { ...item };
    copy.price = 4;
    console.log(item.price, copy.price);
  2. Predict · Activity 2 of 7

    Predict before you read on: text holds JSON. What does this print?

    const text = '{"name": "Ada", "age": 36}';
    console.log(text.name, JSON.parse(text).name);
  3. Practice · Activity 3 of 7

    Fill in the method that turns the JSON text into an object, so that the last line prints 3.

    const text = '{"count": 2}';
    const data = JSON.____(text);
    console.log(data.count + 1);
    const data = JSON.(text);
  4. Practice · Activity 4 of 7

    Match each call to what it gives.

  5. Practice · Activity 5 of 7

    What does this print?

    console.log(JSON.stringify({ a: 1, b: undefined, c: [1, undefined] }));
  6. Brain teaser · Activity 6 of 7

    Brain teaser. The order is saved and loaded again. What does the last line print?

    const order = { total: NaN, when: new Date(0) };
    const copy = JSON.parse(JSON.stringify(order));
    console.log(copy.total, typeof copy.when);
  7. Apply · Activity 7 of 7

    Mini-task: save and load settings. Make a settings object with a theme, a font size and an array of favourites. Turn it into JSON text indented by two spaces and print it. Parse the text back, and check with typeof and Array.isArray that you got an object and an array of favourites. Then try to parse settings with a trailing comma inside try { … } catch (error) { … }, and print the error's name.

    Check your work against this list

Build it yourself

Read the worked example, then write the exercises. Your code runs in your browser or on your computer and is never uploaded.

Worked example

A reading list as JSON

A reading list is turned into JSON text and back. The text is one string with no spaces; with an indent it spreads over lines. Parsing gives real objects again, a round trip gives an independent copy, and an object with NaN, undefined, a Date and a method shows what JSON cannot hold. The last part, a preview of module I4, catches the SyntaxError from JSON with single quotes.

main.js

// A reading list saved as JSON text and loaded back.
const list = [
  { title: "Dune", pages: 412, read: true },
  { title: "Emma", pages: 474, read: false }
];

const text = JSON.stringify(list);
console.log(text);
console.log(typeof text, Array.isArray(list));

console.log(JSON.stringify(list[0], null, 2));

const loaded = JSON.parse(text);
console.log(loaded[1].title, Array.isArray(loaded), typeof loaded[0]);

const copy = JSON.parse(JSON.stringify(list));
copy[0].read = false;
console.log(list[0].read, copy[0].read);

const odd = { score: NaN, note: undefined, when: new Date(0), show() {} };
console.log(JSON.stringify(odd));

try {
  JSON.parse("{'title': 'Dune'}");
} catch (error) {
  console.log(error.name + ": " + error.message);
}

Run it with

node main.js

Output

[{"title":"Dune","pages":412,"read":true},{"title":"Emma","pages":474,"read":false}]
string true
{
  "title": "Dune",
  "pages": 412,
  "read": true
}
Emma true object
true false
{"score":null,"when":"1970-01-01T00:00:00.000Z"}
SyntaxError: Expected property name or '}' in JSON at position 1 (line 1 column 2)
  • JSON.stringify puts double quotes around every key and string, and leaves out all spaces.
  • With null, 2 every level gets its own lines, indented by two spaces.
  • The copy from the round trip changes on its own: list[0].read stays true.
  • NaN became null, the Date a string, and note and show are gone.
  • The SyntaxError names the position of the first character that breaks the rules: the single quote at position 1.
Change it and run it

Tab indents and Shift+Tab outdents. To leave the editor with the keyboard, press Esc, then Tab.

The first run downloads the JavaScript runner (up to 0.1 MB) and keeps it cached. Your code runs in your browser’s own engine and stays on your device.

Exercises

Exercise 1 of 2

Load data and check its shape

parseScores(text) should parse JSON text and return the array it holds. If the text holds anything else, such as an object or a number, it should return an empty array, []. isRecord(value) should return true only for a plain object: not for null, not for an array, not for a number. The starter trusts the text, and its isRecord says yes to null and arrays. Fix both with Array.isArray, typeof and a check for null.

Tab indents and Shift+Tab outdents. To leave the editor with the keyboard, press Esc, then Tab.

The first run downloads the JavaScript runner (up to 0.1 MB) and keeps it cached. Your code runs in your browser’s own engine and stays on your device.

Hints
  1. Hint 1

    Run the tests: parseScores gives back the object it parsed, and isRecord(null) is true, because typeof null is "object".

  2. Hint 2

    Keep the parsed value in a constant, then return Array.isArray(data) ? data : [].

  3. Hint 3

    For isRecord, add two checks with &&: value !== null and !Array.isArray(value).

Show a solution

One way to solve it. Yours can look different and still pass the checks.

// The array in the JSON text, or [] when the text holds something else.
export function parseScores(text) {
  const data = JSON.parse(text);
  return Array.isArray(data) ? data : [];
}

// true only for a plain object: not null, not an array.
export function isRecord(value) {
  return typeof value === "object" && value !== null && !Array.isArray(value);
}
Run it on your computer

Install ECMAScript 2026 or newer. Save these files in one folder, open a terminal in that folder, and run the commands below.

main.js

// The array in the JSON text, or [] when the text holds something else.
export function parseScores(text) {
  return JSON.parse(text);
}

// true only for a plain object: not null, not an array.
export function isRecord(value) {
  return typeof value === "object";
}

main.test.js

import {test} from 'node:test';
import assert from 'node:assert/strict';
import {parseScores, isRecord} from './main.js';

test('parseScores returns the array in the text', () => {
  const got = parseScores('[3, 5, 8]');
  assert.deepEqual(got, [3, 5, 8], `parseScores('[3, 5, 8]') returned ${JSON.stringify(got)}, expected [3,5,8]`);
});

test('parseScores returns [] for an object', () => {
  const got = parseScores('{"scores": [3, 5]}');
  assert.deepEqual(got, [], `parseScores of an object returned ${JSON.stringify(got)}, expected []`);
});

test('parseScores returns [] for a number', () => {
  const got = parseScores('7');
  assert.deepEqual(got, [], `parseScores('7') returned ${JSON.stringify(got)}, expected []`);
});

test('isRecord is true for an object', () => {
  assert.equal(isRecord(JSON.parse('{"a": 1}')), true, 'isRecord of an object should be true');
});

test('isRecord is false for null and for an array', () => {
  assert.equal(isRecord(JSON.parse('null')), false, 'isRecord(null) should be false');
  assert.equal(isRecord(JSON.parse('[1]')), false, 'isRecord of an array should be false');
});

package.json

{
  "type": "module"
}

package.json tells Node.js that the .js files are modules; keep it in the folder.

Run the program:

node main.js

Run the checks (needs learnrun.js in the same folder):

node --test
Download learnrun.js

Exercise 2 of 2

Save data and copy it deeply

toJson(data) should return the JSON text of data, indented by two spaces, ready to save in a file. copyData(data) should return a copy that can change, nested arrays included, without changing data. The data is plain: objects, arrays, strings, numbers and booleans. The starter writes everything on one line, and its spread copy shares the nested arrays. Fix both with JSON.stringify and JSON.parse.

Tab indents and Shift+Tab outdents. To leave the editor with the keyboard, press Esc, then Tab.

The first run downloads the JavaScript runner (up to 0.1 MB) and keeps it cached. Your code runs in your browser’s own engine and stays on your device.

Hints
  1. Hint 1

    Run the tests: toJson gives one long line, and pushing to the copy's items changes the original too.

  2. Hint 2

    JSON.stringify takes the indent as its third argument; the second one is null.

  3. Hint 3

    Turn data into text and parse it back: JSON.parse(JSON.stringify(data)) builds every nested array anew.

Show a solution

One way to solve it. Yours can look different and still pass the checks.

// The JSON text of data, indented by two spaces.
export function toJson(data) {
  return JSON.stringify(data, null, 2);
}

// A copy whose nested arrays and objects are its own.
export function copyData(data) {
  return JSON.parse(JSON.stringify(data));
}
Run it on your computer

Install ECMAScript 2026 or newer. Save these files in one folder, open a terminal in that folder, and run the commands below.

main.js

// The JSON text of data, indented by two spaces.
export function toJson(data) {
  return JSON.stringify(data);
}

// A copy whose nested arrays and objects are its own.
export function copyData(data) {
  return { ...data };
}

main.test.js

import {test} from 'node:test';
import assert from 'node:assert/strict';
import {toJson, copyData} from './main.js';

test('toJson indents by two spaces', () => {
  const got = toJson({ name: 'Ada', tags: ['x'] });
  const expected = '{\n  "name": "Ada",\n  "tags": [\n    "x"\n  ]\n}';
  assert.equal(got, expected, `toJson returned ${JSON.stringify(got)}, expected ${JSON.stringify(expected)}`);
});

test('copyData copies every key', () => {
  const got = copyData({ owner: 'Ada', items: ['tea'] });
  assert.deepEqual(got, { owner: 'Ada', items: ['tea'] }, `copyData returned ${JSON.stringify(got)}`);
});

test('Changing the nested array of the copy leaves the original alone', () => {
  const data = { owner: 'Ada', items: ['tea'] };
  const copy = copyData(data);
  copy.items.push('milk');
  assert.deepEqual(data.items, ['tea'], `the original's items became ${JSON.stringify(data.items)}, expected ["tea"]`);
});

package.json

{
  "type": "module"
}

package.json tells Node.js that the .js files are modules; keep it in the folder.

Run the program:

node main.js

Run the checks (needs learnrun.js in the same folder):

node --test
Download learnrun.js

Common mistakes

A trailing comma in JSON

const text = '{"name": "Ada", "age": 36,}';
const user = JSON.parse(text);
console.log(user.name);

What Node.js prints

SyntaxError: Expected double-quoted property name in JSON at position 26 (line 1 column 27)

Why, and the fix

JavaScript allows a comma after the last item, JSON does not. After the comma, JSON.parse expects another key in double quotes and finds }, at position 26 (counting from 0). Remove the comma: '{"name": "Ada", "age": 36}'. JSON written by JSON.stringify never has this problem; it comes from JSON typed by hand.

Single quotes in JSON

const text = "{'name': 'Ada'}";
const user = JSON.parse(text);
console.log(user.name);

What Node.js prints

SyntaxError: Expected property name or '}' in JSON at position 1 (line 1 column 2)

Why, and the fix

JSON keys and strings take double quotes only, so the single quote at position 1 is not a property name. Write the JSON with double quotes, and wrap the JavaScript string in single quotes or backticks: '{"name": "Ada"}'. Firefox and Safari word this message differently; the position is the clue.

Parsing something that is already an object

const user = { name: "Ada" };
const data = JSON.parse(user);
console.log(data.name);

What Node.js prints

SyntaxError: "[object Object]" is not valid JSON

Why, and the fix

JSON.parse takes text. Given an object, it first turns it into a string, "[object Object]", which is not JSON. user is already an object: use it directly, user.name. Parse only strings that hold JSON, such as text read from a file or a server, or made by JSON.stringify.

JavaScript in the browser: your browser’s own engine, in a sandboxed worker. Syntax errors are located with acorn 8.18.0, MIT. Licence and source

Exit ticket

5 questions, no hints. Score 80% or more to complete the lesson.

Finish every activity above to unlock the exit ticket.

Report a problem

Spotted something wrong or unclear? Say what, and it will be checked and fixed.

#

At least 20 characters.

Only if you want a reply.

Key ideas

JSON is text with strict rules

JSON is a text format for data that looks like JavaScript literals: objects, arrays, strings, numbers, true, false and null. In a program, JSON is always a string, such as '{"name": "Ada"}'; it is not an object until you parse it, so text.name is undefined. The rules are stricter than JavaScript's: every key and every string in double quotes, never single quotes; no trailing comma after the last item; no comments; no undefined, NaN, Infinity or functions. Numbers are written plainly, with no leading zero such as 01. Not every object literal is valid JSON, but every piece of JSON is a valid JavaScript literal.

JSON.parse reads text, and may refuse it

JSON.parse(text) builds the value the text describes: '{"a": 1}' becomes an object, '[1, 2]' an array, '"hi"' a string, 'null' null. Text that breaks a rule stops the program with a SyntaxError, and V8 says where: a trailing comma in {"a": 1,} gives Expected double-quoted property name in JSON at position 8. Passing an object instead of text fails too: JSON.parse turns it into "[object Object]" first. Data from outside may not have the shape you expect, so check it: Array.isArray(data) for an array, typeof data === "object" && data !== null for an object. A preview of I4: try { … } catch (error) { … } lets the program go on after a SyntaxError.

JSON.stringify writes text, and drops what JSON cannot hold

JSON.stringify(value) gives the JSON text of a value, with no spaces: {"a":1}. A third argument indents it, one level per line: JSON.stringify(data, null, 2) uses two spaces; the second argument, null here, stays empty in this course. Values JSON cannot hold are dropped or changed: a key whose value is undefined or a function disappears, and inside an array they become null; NaN and Infinity become null; a Date becomes a string such as "1970-01-01T00:00:00.000Z". JSON.parse(JSON.stringify(data)) is a deep copy, nested arrays included, but only for data made of plain objects, arrays, strings, numbers, booleans and null.

Sources

Last reviewed October 3, 2026