JavaScript JSON
Learn to work with JSON in JavaScript: parse and stringify data, use revivers and replacers, pretty-print output, and deep-copy objects.
JavaScript Object Notation (JSON) is the backbone of data interchange on the web. Almost every API you call returns JSON, every config file you read is likely JSON, and every time the browser talks to a server, JSON is usually the format on the wire. This guide covers everything you need to work with JSON confidently in JavaScript: the syntax, the two core methods (JSON.parse and JSON.stringify), how to format and filter output, how to handle dates and deep copies, and the gotchas that trip people up in production.
What JSON is and why it matters
JSON is a lightweight, text-based data format that is easy for humans to read and write and easy for machines to parse and generate. It is language independent — even though its syntax is borrowed from JavaScript (Standard ECMA-262), virtually every language (Python, Java, C#, Go, Rust, PHP) can read and write it. That universality is exactly why it became the default format for REST APIs, config files (package.json, tsconfig.json), and localStorage.
The key thing to understand: JSON is a string, not an object. A JSON string only becomes a usable JavaScript object after you parse it, and a JavaScript object only becomes JSON after you stringify it. Most JSON work is moving between these two states.
Basic JSON syntax
JSON syntax is a strict subset of JavaScript object literal syntax:
- Data is written as
"key": valuepairs - Pairs are separated by commas
- Curly braces
{}hold objects, square brackets[]hold arrays - Keys must be double-quoted strings (single quotes and unquoted keys are invalid)
- Values may be a string, number, boolean,
null, object, or array — but notundefined, a function, or aDate
{
"name": "John",
"age": 30,
"isDeveloper": true,
"languages": ["JavaScript", "Python", "Rust"]
}Parsing JSON with JSON.parse()
JSON.parse() turns a JSON string into a JavaScript object (or array, number, etc.) that you can read and manipulate. In real code — especially when the JSON comes from a network request or user input — always wrap it in a try...catch, because malformed JSON throws a SyntaxError.
Accessing nested data
Parsed JSON behaves like any other object, so you reach nested values with dot or bracket notation. This is the most common thing you do with an API response.
Transforming values with a reviver
JSON.parse() accepts an optional reviver function as a second argument. It is called for every key/value pair, and whatever it returns becomes the final value — handy for converting types as you parse. A classic use is restoring Date objects (JSON has no date type, so dates arrive as strings).
Stringifying objects with JSON.stringify()
JSON.stringify() does the reverse: it converts a JavaScript value into a JSON string, ready to send over the network or save to storage.
Pretty-printing with indentation
The third argument controls spacing. Pass a number (spaces) or a string to produce readable, indented JSON — useful for logs, config files, and debugging.
Filtering properties with a replacer
The second argument is a replacer. As an array of keys it whitelists which properties to keep — a quick way to strip out sensitive fields like passwords before sending data.
As a function, the replacer is called for every key and lets you transform or drop values. Returning undefined from it omits that property.
Custom serialization with toJSON
You can control how a specific object is serialized by giving it a toJSON() method. When JSON.stringify() encounters an object with this method, it calls toJSON() and serializes whatever it returns. (This is exactly how Date objects produce ISO strings — they have a built-in toJSON.)
What stringify silently drops
JSON.stringify() is lossy by design. Knowing what it skips prevents confusing bugs:
undefined, functions, andSymbolvalues are omitted from objects (and becomenullinside arrays).Dateobjects become ISO strings (via theirtoJSON).NaNandInfinitybecomenull.BigIntthrows aTypeError.- Circular references throw a
TypeError.
Advanced techniques and common patterns
Beyond the two core methods, day-to-day JSON work involves deep copying, handling dates, and processing arrays of records.
Deep copying objects
The JSON.parse(JSON.stringify(obj)) trick creates a deep copy — a clone whose nested objects are fully independent of the original, so mutating one never affects the other.
It is quick and dependency-free, but inherits all of stringify's limitations: it drops functions, undefined, and Symbols, turns Dates into strings, and throws on circular references. For a faithful clone, prefer the built-in structuredClone(), which handles dates, maps, sets, and cycles.
Working with dates
JSON has no date type, so dates travel as ISO 8601 strings. JSON.stringify writes them automatically, but on parse you get a plain string back — you must rebuild the Date yourself, either with new Date(...) or a reviver (shown earlier).
Processing arrays of records
API responses are usually arrays of objects. After parsing, the standard array methods (forEach, map, filter) do the rest.
JSON and the browser: fetch and localStorage
Two everyday places JSON shows up:
fetch:response.json()reads the body and parses it for you, so you rarely callJSON.parseon a fetch result directly —const data = await response.json();. See the Fetch API guide.localStorage: it stores strings only, so stringify on the way in and parse on the way out:localStorage.setItem('user', JSON.stringify(user))thenJSON.parse(localStorage.getItem('user')).
Conclusion
JSON is the format you'll touch most often as a JavaScript developer, and it really comes down to two methods: JSON.parse() to turn a string into a value you can use, and JSON.stringify() to turn a value back into a string. Layer on the reviver and replacer arguments for type conversion and filtering, indentation for readable output, and toJSON for custom serialization, and you can handle almost any data-interchange task.
Keep the lossy edge cases in mind — dropped undefined/functions, stringified dates, and circular-reference errors — and reach for structuredClone() when you need a true copy rather than a JSON round-trip. With these tools you can confidently move data between your app, the server, and browser storage.