---
title: JSON
source: 'https://academia.sh/en/courses/javascript-fundamentals/json'
course: 'JavaScript Fundamentals'
language: en
updated: '2026-08-17T18:09:46+00:00'
license: 'CC BY-SA 4.0'
---

# JSON

Serialization and parsing, values the format does not support, circular references, callbacks that adapt the conversion, and the limit of number precision.

The previous lesson produced readable report text. That text was for humans; it is not
a format the machine can read back. This lesson takes up JSON: converting data to text
and reading it back.

JSON is a data format derived from JavaScript syntax but independent of it. This origin
makes the two look close to each other while not fully overlapping. Knowing the
distinction prevents silent data loss.

## Serialization and Parsing

```js
const record = { station: "A1", temperature: 21.5, humidity: null, validated: false };
const text = JSON.stringify(record);
console.log(text);
console.log(typeof text);
console.log(JSON.stringify(record, null, 2));
const back = JSON.parse(text);
console.log(back, back.temperature + 1);
console.log(record === back);
```

```
{"station":"A1","temperature":21.5,"humidity":null,"validated":false}
string
{
  "station": "A1",
  "temperature": 21.5,
  "humidity": null,
  "validated": false
}
{ station: 'A1', temperature: 21.5, humidity: null, validated: false } 22.5
false
```

`JSON.stringify` converts a value to text; the third argument is the indent amount and
produces readable output. `JSON.parse` produces a new value from text.

The last line matters: the result of the conversion is a **new object**. By the
identity rule from the Equality Comparisons lesson, it is not equal to the original
object. This is also a way of taking a deep copy — but only for values the format
supports.

The third block in the output is JSON text: keys are double-quoted. The object display
at the end is the runtime's inspection format; the unquoted keys and single-quoted text
come from there. The two are different things.

## Values the Format Does Not Support

JSON's set of values is narrower than the language's set of values. Unsupported values
are handled silently:

```js
const record = {
  station: "A1",
  missing: undefined,
  fn: () => 1,
  symbol: Symbol("x"),
  invalid: NaN,
  infinite: Infinity,
};
console.log(JSON.stringify(record));
console.log(JSON.stringify([undefined, () => 1, NaN]));
```

```
{"station":"A1","invalid":null,"infinite":null}
[null,null,null]
```

Two different behaviors are at play. On object properties, `undefined`, function, and
symbol values are **skipped entirely**: the key never shows up in the output. On array
elements, the same values are converted to `null`, because the array's length has to be
preserved.

`NaN` and `Infinity` become `null` in both cases. This is because the format does not
recognize these values at all. The result is silent: an invalid measurement turns, once
parsed, into `null` — which means "deliberately empty." The `null`–`undefined`
distinction established in the Primitive Types lesson does not survive the conversion.

In two cases, an error is thrown:

```js
const a = { station: "A1" };
a.itself = a;
try {
  JSON.stringify(a);
} catch (error) {
  console.log(error.name);
}
try {
  JSON.stringify({ counter: 1n });
} catch (error) {
  console.log(error.name + ": " + error.message);
}
```

```
TypeError
TypeError: Do not know how to serialize a BigInt
```

A **circular reference** — an object directly or indirectly containing itself — would
produce an infinite text. Big integers are rejected because, as discussed in the BigInt
lesson, the format has no precision guarantee for them. The first error's message
varies by runtime, so only its name was printed.

## Adapting the Conversion

Both methods can take a callback. In serialization, the callback is called for every
key–value pair, and the value it returns gets written; returning `undefined` skips the
field.

```js
const record = {
  station: "A1",
  timeNs: 1700000000123456789n,
  temperature: 21.5,
  secret: "do not write",
};

const text = JSON.stringify(record, (key, value) => {
  if (key === "secret") return undefined;
  if (typeof value === "bigint") return value.toString();
  return value;
});
console.log(text);

const back = JSON.parse(text, (key, value) =>
  key === "timeNs" ? BigInt(value) : value,
);
console.log(back.timeNs === record.timeNs, typeof back.timeNs);
```

```
{"station":"A1","timeNs":"1700000000123456789","temperature":21.5}
true bigint
```

The problem left open in the BigInt lesson is finished here: the value was written as
text, converted back on reading, and preserved down to its last digit. The `secret`
field did not enter the output.

The two callbacks are **symmetric** and should be designed together: whichever field
the writing side converted to which format, the reading side has to convert that same
field back. Otherwise the type changes silently.

Instead of a callback, serialization can also be given a list of fields:

```js
const record = { station: "A1", temperature: 21.5, humidity: 48 };
console.log(JSON.stringify(record, ["station", "temperature"]));
```

```
{"station":"A1","temperature":21.5}
```

## The Limit of Number Precision

Numbers in JSON text are converted to the language's number type when parsed. The safe
integer range from the Primitive Types lesson applies here too:

```js
console.log(JSON.parse("9007199254740993"));
console.log(JSON.parse("0.1") + JSON.parse("0.2"));
console.log(JSON.stringify(0.1 + 0.2));
```

```
9007199254740992
0.30000000000000004
0.30000000000000004
```

The first line is a silent loss: an integer correctly written in the text landed on a
different value once read. The format could carry this value; what loses it is not the
parser, it is the **target type**. For lossless transfer, the value is written as
text — this is the callback pattern above.

The last two lines show floating-point representation surviving the conversion: the
written text returns to the same bit pattern.

## Parsing Is Strict

JSON's grammar is narrower than JavaScript's object syntax:

```js
const broken = ['{"a":1,}', "{'a':1}", "{a:1}", ""];
for (const m of broken) {
  try {
    JSON.parse(m);
    console.log(JSON.stringify(m), "-> valid");
  } catch (error) {
    console.log(JSON.stringify(m), "->", error.name);
  }
}
```

```
"{\"a\":1,}" -> SyntaxError
"{'a':1}" -> SyntaxError
"{a:1}" -> SyntaxError
"" -> SyntaxError
```

All four are valid or harmless writings in JavaScript source; not in JSON. A trailing
comma, single quotes, an unquoted key, and empty text are all rejected. This strictness
is what lets the format be read the same way across different languages.

The error name is `SyntaxError`; the message text varies by parser. The rule from the
Error Handling lesson applies here: branching is done on the name.

## Applying It to the Measurement Script

The course's thread can now write a full round trip: read from text, transform, write
back to text.

```js
const incoming = `[
  {"station":"A1","temperature":21.5,"humidity":48},
  {"station":"A2","temperature":19.75,"humidity":null},
  {"station":"B1","temperature":23,"humidity":41}
]`;

const records = JSON.parse(incoming);
const enriched = records.map((r) => ({
  ...r,
  deviation: Number((r.temperature - 20).toFixed(2)),
  hasHumidity: r.humidity != null,
}));

console.log(JSON.stringify(enriched, null, 2));
```

```
[
  {
    "station": "A1",
    "temperature": 21.5,
    "humidity": 48,
    "deviation": 1.5,
    "hasHumidity": true
  },
  {
    "station": "A2",
    "temperature": 19.75,
    "humidity": null,
    "deviation": -0.25,
    "hasHumidity": false
  },
  {
    "station": "B1",
    "temperature": 23,
    "humidity": 41,
    "deviation": 3,
    "hasHumidity": true
  }
]
```

Three decisions were made together. The transformation produced new objects; by the
rule from the Objects lesson, the input was not changed. The deviation value was
rounded with `toFixed` and converted back to a number with `Number` — otherwise it
would have been written to JSON as text. The `humidity` field's presence was tested
with a `!= null` check, so a `null` value was also counted as missing.

If the text being parsed comes from outside, parsing should be wrapped in a
`try`/`catch`; broken input should not stop the program.

## Summary

- `JSON.stringify` converts a value to text, `JSON.parse` converts text to a new value;
  the result of the conversion is not equal to the original object.
- On object properties, `undefined`, function, and symbol are skipped; on array
  elements, they are converted to `null`. `NaN` and `Infinity` become `null` in every
  case.
- Circular references and big integers throw during serialization.
- Callbacks make lossless transfer of unsupported types possible; the writing and
  reading sides are written symmetrically.
- JSON numbers are converted to the language's number type; integers above the safe
  range are silently rounded.
- JSON's grammar is strict: a trailing comma, single quotes, and an unquoted key are
  all rejected.

## Next Step

The measurement records are still timeless: it is not known when any given reading was
taken. The next lesson takes up date and time representation — the timestamp concept,
the timezone/local-time distinction, parsing traps, and formatting. The course's
closing will happen there too.
