Skip to Content
JavaScript Intermediate: Write Code That Doesn't Fall Apart When the Data Changes
Intermediate
0 likes
Courses
JavaScript Intermediate: Write Code That Doesn't Fall Apart When the Data Changes

Working With JSON: parse, stringify and What Breaks

Turn JSON text into data and back, read the real parse error messages, and see what stringify quietly drops or changes. After this, a bad response gets caught instead of crashing the page.

Course content

Sign in to save your progress.

Course Home

Skip ahead: If you already know why JSON.parse throws on undefined and what JSON.stringify drops, skip to "What JSON.stringify quietly changes".

JSON is how data moves between programs: an API response, a config file, something saved in localStorage. It looks like a JavaScript object, which is the trap. It's a text format with stricter rules, and JavaScript has two functions to cross between them: JSON.parse turns text into data, JSON.stringify turns data into text. Most bugs happen at that crossing.

The rules JSON has and JavaScript doesn't

  • Property names in double quotes: {"name": "Ana"}, not {name: "Ana"} and not {'name': 'Ana'}.
  • Strings in double quotes.
  • No trailing commas.
  • No comments.
  • Values: strings, numbers, true, false, null, arrays, objects. No undefined, no functions, no dates (dates travel as strings).

JSON.parse: text in, data out

const text = '{"id": 1042, "customer": "Ana", "paid": true, "items": ["rice", "eggs"]}';
const order = JSON.parse(text);
console.log(order.customer, order.items.length, typeof order.paid);
Ana 2 boolean

When the text breaks a rule, JSON.parse throws a SyntaxError. Here's what Node 24 (and Chrome) actually says for the common cases:

const samples = ['{name: "Ana"}', '{"a": 1,}', "", "undefined", '<!DOCTYPE html><html>'];
for (const text of samples) {
  try {
    JSON.parse(text);
  } catch (error) {
    console.log(error.message);
  }
}
Expected property name or '}' in JSON at position 1 (line 1 column 2)
Expected double-quoted property name in JSON at position 8 (line 1 column 9)
Unexpected end of JSON input
"undefined" is not valid JSON
Unexpected token '<', "<!DOCTYPE "... is not valid JSON

Read the message for a clue to the cause:

  • Expected property name: a key without double quotes. Someone wrote JavaScript, not JSON.
  • Expected double-quoted property name near the end: usually a trailing comma.
  • Unexpected end of JSON input: the text was empty or cut off. Often an empty response from a server.
  • "undefined" is not valid JSON: you passed undefined, usually a property that doesn't exist, like JSON.parse(response.data) when the field is called something else. (A missing localStorage key is different: getItem gives null, and JSON.parse(null) quietly returns null.)
  • Unexpected token '<': you got HTML, not JSON. Almost always an error page or a wrong URL.

The tutorial Fix "Unexpected token in JSON" Errors goes through how to find which one you have.

⚠ Warning: Wrap JSON.parse in try/catch whenever the text comes from outside your code: a server, a file, a user, localStorage. You don't control that text. One bad response shouldn't take down the whole page.

JSON.stringify: data in, text out

const order = { id: 1042, customer: "Ana", items: ["rice"] };
console.log(JSON.stringify(order));
console.log(JSON.stringify(order, null, 2));
{"id":1042,"customer":"Ana","items":["rice"]}
{
  "id": 1042,
  "customer": "Ana",
  "items": [
    "rice"
  ]
}

The third argument indents it for humans. Use it for logs and files you'll read, not for data you send.

What JSON.stringify quietly changes

This is the part that doesn't throw. It just loses data.

const order = {
  id: 1042,
  note: undefined,
  print: () => "hi",
  discount: NaN,
  createdAt: new Date(Date.UTC(2026, 9, 15, 8, 30)),
};
const copy = JSON.parse(JSON.stringify(order));
console.log(copy);
console.log(typeof copy.createdAt);
{ id: 1042, discount: null, createdAt: '2026-10-15T08:30:00.000Z' }
string
  • undefined and functions: gone.
  • NaN and Infinity: become null.
  • Dates: become strings. Parse them back on purpose with new Date(copy.createdAt).
  • Class instances: become plain objects, without their methods or private fields.

And two things that do throw: BigInt values (TypeError: Do not know how to serialize a BigInt) and objects that point to themselves (TypeError: Converting circular structure to JSON).

💡 Tip: JSON.parse(JSON.stringify(x)) is an old way to deep-copy data. Because of the list above, it silently breaks dates and drops fields. Use structuredClone(x) instead. It keeps dates as dates.

Common mistakes

  • Parsing something that's already an object. JSON.parse({}) turns it into the text [object Object] first and throws "[object Object]" is not valid JSON.
  • Building JSON by gluing strings together. One quote in a name breaks it. Use JSON.stringify.
  • Expecting createdAt to still be a Date after a round trip.
  • Not catching parse errors from a server.
  • Storing numbers like order IDs that are too big for JavaScript numbers (above 9007199254740991) as numbers. Send them as strings.

What happens if you skip this

Nothing, for a while. Then the server returns an error page instead of JSON, JSON.parse throws on the first <, and your whole page stops with a message nobody on your team has seen before. Or a date comes back as a string, order.createdAt.getFullYear() throws, and you spend an hour on a bug that was a single missing new Date().

What do you get from JSON.stringify({ a: undefined, b: null })? Try it.

About

Turn JSON text into data and back, read the real parse error messages, and see what stringify quietly drops or changes. After this, a bad response gets caught instead of crashing the page.

Duration
5 min
Last update
08/26/2026
Was this helpful?
​ 0 ​ 0

Comments (0)

Sign in to comment

No comments yet.