JavaScript JSON Table
| Piece | What it does | Field note |
|---|---|---|
JSON.stringify(obj) | Object to string | Silently drops functions, undefined, Symbol; NaN becomes null |
toJSON() method | Custom serialization | Date has one - d.toJSON() = toISOString, which is why dates survive |
JSON.parse(str) | String to object | THROWS on malformed input - always try/catch external JSON |
replacer (arg 2) | Filter or transform | Array of keys to keep, or function(key, value) to rewrite |
space (arg 3) | Pretty printing | JSON.stringify(o, null, 2) - two-space indent for logs |
key order | Strings keep insertion | Integer-LIKE keys jump FIRST, ascending - {b:1, 2:2, a:3} serializes 2,a,b |
Date roundtrip | String on the wire | parse does NOT revive - new Date(json.date) is your job on the way out |
localStorage pair | setItem(k, stringify(v)) | Objects convert both directions - the storage boundary is text |
JSON is a STRING format with JavaScript-friendly syntax: stringify serializes objects to text, parse revives text to objects, and the boundary between them eats things silently - functions, undefined and Symbols vanish, NaN becomes null, Dates become ISO strings through their toJSON method.
Bottom line: JSON.parse throws on malformed input, so any string from outside your code - fetch responses, localStorage, postMessage - goes through try/catch. Pretty-print with the third argument (null, 2), filter with the replacer, and revive Dates explicitly: parse hands you strings, not Date objects.
The honest part: key order is not random but is not what it looks like - string keys keep insertion order, while integer-LIKE keys ('2', '42') jump to the front in ascending numeric order. Code that depends on object key order for mixed keys is depending on a quirk; Map is the container that honors true insertion order.
How to use
- Guard every external parse: try { const data = JSON.parse(raw); } catch (e) { /* show a fallback */ } - truncated or hostile input throws, and unguarded throws kill the flow.
- Pretty-print for logs: console.log(JSON.stringify(state, null, 2)) - the two-space indent turns opaque blobs into diffable output; replacer can hide secrets: JSON.stringify(user, ['name', 'id']).
- Roundtrip dates deliberately: JSON.stringify(new Date()) stores an ISO string; new Date(stored) revives it - parse() will not do that step for you.
Frequently asked questions
Why did my function or undefined property disappear after JSON roundtrip?
JSON is a data format, not a JS-state format: stringify serializes only values that have a JSON representation, so functions, undefined, and Symbols are dropped without error - an object with an undefined property simply loses that key. NaN and Infinity, which JSON cannot express either, become null. The consequences: default values must be re-applied after parse (reviver or a merge), and 'lost' fields after an API roundtrip usually mean the value was undefined at stringify time, not that the network ate it.
What are the replacer and space arguments actually for?
They are stringify's two control knobs. The replacer (second argument) filters or transforms: pass an array of keys to whitelist exactly those fields (the quick way to strip secrets before logging), or a function (key, value) that runs bottom-up on every value and can rewrite, redact or drop - returning undefined removes the key. The space argument (third) controls indentation: a number or string, where null, 2 is the canonical two-space pretty print. Together: JSON.stringify(obj, ['id', 'email'], 2) is one line of safe, readable debug output.
Why does my Date come back from JSON as a string?
Because Date owns a toJSON method: stringify calls it and stores the ISO string (which is why dates survive JSON at all - most objects with methods do not). Parse does the reverse only for data, never for types - it cannot know a string was once a Date. The revival step is yours: new Date(obj.createdAt) after parse, or the reviver argument on parse (a function that can transform values during parsing). The same pattern applies to Map, Set and RegExp - none survive JSON natively; convert to arrays or strings first and reconstruct after.
Why do integer-like keys reorder my object?
The engine keeps integer-like property keys ('2', '17', '0') in a separate ascending numeric order, ahead of all string keys, which keep insertion order. So {b: 1, 2: 2, a: 3} serializes as {"2": 2, "b": 1, "a": 3} - and JSON.parse does not restore your original insertion order either, because the order information was already lost when the property was stored. For mixed or order-sensitive keys (timestamps as keys, numeric ids mixed with names), Map is the correct container: it iterates in true insertion order and stringifies through an explicit conversion when needed.