ToolzYard Blog

Developer guides and tutorials

JSON Basics Guide

What is JSON? Meaning, Examples, Uses, and Benefits for Developers

Published: March 14, 2026 • Updated: July 10, 2026 • By , Founder of ThreeWorks

JSON is the format everyone learns in an afternoon and keeps getting surprised by for years. The whole syntax fits on a napkin, which is exactly why it spread everywhere. But that same minimalism hides a set of sharp edges that only show up in production: numbers that quietly lose digits, values that vanish on a round trip, and documents that are technically valid yet still wrong. This guide is about those edges — the things that separate "I can read JSON" from "I trust JSON with a payment record."

If you want to pretty-print or check a specific document while reading, keep the formatter and validator open in another tab. Everything below is about understanding the format itself.

What JSON actually is

JSON stands for JavaScript Object Notation, but it stopped being a JavaScript feature a long time ago. It is pinned down by two documents that agree on the same grammar: RFC 8259 (the IETF standard) and ECMA-404 (the syntax specification). Both define JSON purely as a text interchange grammar — not a programming language, not a schema, not a database. A JSON document is just a sequence of Unicode characters that describes exactly one value.

That "one value" is the detail most people miss. A JSON document does not have to be an object. Every one of these is a complete, valid JSON document on its own:

{ "name": "John", "age": 30 }
[1, 2, 3]
"hello"
42
true
null

So an API that returns a bare true or a top-level array is still returning valid JSON. Code that assumes the top level is always an object breaks the first time it meets one of these.

The value types JSON has — and the ones it leaves out

JSON has exactly six value types: string, number, boolean (true/false), null, object, and array. That is the entire type system. Strings must use double quotes, and keys are just strings, so keys must be double-quoted too.

What matters more is the list of everyday types JSON does not have, because these are where real data loss hides:

  • No date or time type — dates travel as strings, and nothing forces them to be valid
  • No integer vs float distinction — there is only "number"
  • No undefined, no NaN, and no Infinity
  • No comments and no trailing commas
  • No binary type — bytes have to be Base64-encoded into a string

None of these are oversights; they were deliberately cut to keep the grammar tiny. But every one becomes a trap the moment you assume JSON can carry something it cannot.

The number trap: JSON has no integers

This is the single most expensive JSON gotcha in production. JSON numbers have no size limit on paper, but almost every parser reads them into an IEEE-754 double-precision float — the same type as JavaScript's Number. A double can represent every integer exactly only up to 253 − 1, which is 9007199254740991 (JavaScript exposes it as Number.MAX_SAFE_INTEGER). Above that, integers snap to the nearest representable value — silently, with no error.

JSON.parse('{"id": 9007199254740993}')
// { id: 9007199254740992 }   ← the last digit changed

This is not a rare edge case. Twitter/X, Discord, and other systems that mint Snowflake-style 64-bit IDs routinely produce numbers larger than 253. Database BIGINT primary keys do too. If any of those arrive as bare JSON numbers and pass through a JavaScript parser, the low digits can change and you end up fetching the wrong record. The standard fix is to serialize large IDs as strings"id": "9007199254740993" — so the exact digits survive the trip. Twitter's API famously returns both id and id_str for precisely this reason.

Numbers lose precision on the way out too. Serialize 1.0 and it comes back as 1; the fact that it was ever a float is gone. Trailing zeros disappear, and very long decimals get rounded to what a double can hold. If you need exact decimals — money, especially — keep them in a string and do the math with a decimal library, never with raw JSON numbers.

The round trip that quietly deletes your data

JSON.parse(JSON.stringify(value)) is the most common way developers "deep clone" an object, and it is lossy in ways that never announce themselves. JSON.stringify does not throw on values it cannot represent — it drops or mangles them:

const before = {
  id: 1,
  updated: new Date(),      // Date object
  score: NaN,               // not-a-number
  note: undefined,          // undefined
  render: () => 42,         // function
  tag: Symbol("x")          // symbol
};

JSON.parse(JSON.stringify(before));
// { id: 1, updated: "2026-07-09T...Z", score: null }

Look at what survived. undefined, the function, and the symbol were deleted entirely — the keys are simply gone. NaN (and Infinity) became null. And the Date was flattened into an ISO 8601 string; when you parse it back you get a string, not a Date, so result.updated.getFullYear() now throws. A "clone" that changes types and drops keys is a bug waiting for a code path that depended on one of those fields. When you actually need a faithful copy, reach for structuredClone(), which preserves dates, NaN, and undefined.

Why pasting a JavaScript object fails to parse

A frequent confusion: you copy an object straight out of your JavaScript source, paste it into a validator, and it gets rejected — even though it is perfectly good JavaScript. JSON is a strict subset of JavaScript's object syntax, and JavaScript allows several things JSON forbids:

{
  name: 'John',            // unquoted key + single quotes
  role: "developer",
  tags: ["json", "api",],  // trailing comma
  // primary contact          comment
}

Every problem line above is legal JavaScript and illegal JSON. JSON requires double quotes on both keys and strings, bans the trailing comma after the last item, and has no comment syntax at all. This is exactly why config files that "look like JSON" but allow comments — tsconfig.json, VS Code's settings.json — are actually JSONC or JSON5, separate dialects that a standard JSON.parse will reject.

Duplicate keys are legal JSON — and that is dangerous

Here is one that surprises experienced developers: an object with the same key twice is not a syntax error. RFC 8259 says names should be unique but does not require it, so the behavior is implementation-defined.

{ "role": "user", "role": "admin" }

Most parsers (including JavaScript's JSON.parse) keep the last value, so you get admin. Some keep the first. A few reject the document. This gap has shown up in real security bypasses: a validator or proxy reads the first role and approves the request, while the backend parser reads the last one and grants elevated access. Because it passes syntactic validation, no ordinary "is this valid JSON?" check will flag it — you have to look for it deliberately.

The invisible character that breaks JSON.parse

A UTF-8 byte-order mark (BOM) — the three bytes EF BB BF that some Windows editors and export tools prepend to files — is invisible in every editor but sits before the opening brace. RFC 8259 says JSON text must not begin with a BOM, and strict parsers obey: JSON.parse throws Unexpected token at position 0 on a file that looks completely normal on screen. If a document validates when you retype it by hand but fails when loaded from disk, an invisible BOM (or a stray zero-width space) is the usual culprit. Stripping the leading BOM before parsing fixes it.

Tools for working with these edges

When a document surprises you, these browser-based tools make the structure and the failure point visible:

Why these edges are worth knowing

None of this makes JSON a bad format. It is the right default for APIs and config precisely because it is small and universal. But "simple to read" is not the same as "safe to assume." The developers who get burned are the ones who treat JSON as if it round-trips every value losslessly, preserves every integer exactly, and rejects everything that is semantically wrong. It does none of those things.

The practical takeaways are short: send large IDs and exact decimals as strings; never rely on JSON.stringify to clone anything holding dates, functions, or undefined; remember that a document can be valid and still be wrong (duplicate keys, missing fields); and watch for invisible bytes when a file fails to parse for no visible reason.

Conclusion

JSON earns its place by being the smallest format that still covers most real data. The catch is that its simplicity is on the surface; the type system underneath is narrower than it looks, and the parsers that read it disagree at the margins. Learn where those margins are — the 253 number ceiling, the lossy round trip, legal duplicate keys, the invisible BOM — and JSON goes from a format you can read to one you can actually trust with data that matters.

Frequently Asked Questions

Is JSON a file type or a data format?

It is a text interchange format defined by RFC 8259 and ECMA-404, independent of any file extension. The .json extension and the application/json MIME type are conventions layered on top; the same syntax is used inline in HTTP bodies, log lines, and database columns with no file involved.

Why do large ID numbers change when I parse JSON?

Most parsers read JSON numbers into IEEE-754 doubles, which hold integers exactly only up to 253 − 1 (9007199254740991). A 64-bit ID above that snaps to the nearest representable value with no error. Serialize such IDs as strings to preserve every digit.

Are duplicate keys valid JSON?

Yes. RFC 8259 recommends unique keys but does not require them, so duplicates are not a syntax error. Behavior is implementation-defined: most parsers keep the last occurrence, some keep the first, a few reject it — which is why duplicates have been used in security bypasses.

Why does my JavaScript object fail to parse as JSON?

JSON is a strict subset of JavaScript syntax. Unquoted keys, single quotes, trailing commas, and comments are all legal JavaScript but illegal JSON. Files that allow comments, like tsconfig.json, are the JSONC or JSON5 dialects, not standard JSON.

Can I trust JSON.parse(JSON.stringify(x)) as a deep clone?

Only for plain string, number, boolean, null, object, and array data. It silently drops undefined, functions, and symbols, turns NaN and Infinity into null, and converts Date objects into strings that do not parse back as dates. Use structuredClone() when those need to survive.