JSON & Data
JSON syntax and rules, JSON.stringify and JSON.parse with replacer/reviver, deep cloning, structuredClone, and the dangers of eval.
10 questions
JuniorTheoryCommonWhat is JSON and what value types may a JSON document contain?
What is JSON and what value types may a JSON document contain?
JSON is a language-independent text format for structured data. It allows objects with double-quoted string keys, arrays, strings, finite numbers, true, false, and null. It cannot represent functions, undefined, symbols, BigInt, dates, or comments, and every key and string must use double quotes.
Common mistakes
- ✗Using single quotes or unquoted keys, which JSON rejects — only double quotes are valid
- ✗Assuming JSON supports comments or trailing commas like a JavaScript literal
- ✗Thinking
undefined, functions, or dates have a native JSON representation
Follow-up questions
- →How is a
Datetypically carried through JSON if the format has no date type? - →Why is JSON described as a subset of JavaScript object literal syntax rather than identical to it?
JuniorTheoryCommonWhat does JSON.parse do, and what happens when the input is invalid JSON?
What does JSON.parse do, and what happens when the input is invalid JSON?
JSON.parse turns a JSON string into the corresponding JavaScript value (object, array, string, number, boolean, or null). If the text is not valid JSON — wrong quotes, a trailing comma, undefined, a stray token — it throws a SyntaxError rather than returning null, so calls on untrusted input should be wrapped in try/catch.
Common mistakes
- ✗Expecting
JSON.parseto returnnullon bad input instead of throwingSyntaxError - ✗Parsing untrusted data without a
try/catch, so a malformed payload crashes the code - ✗Thinking
JSON.parseaccepts single quotes or trailing commas like a JavaScript literal
Follow-up questions
- →How does the second
reviverargument let you transform values during parsing? - →Why is
JSON.parsesafer thanevalfor turning text into a value?
JuniorTheoryCommonWhat does JSON.stringify do with functions, undefined, NaN, and Infinity?
What does JSON.stringify do with functions, undefined, NaN, and Infinity?
JSON.stringify serializes a value to a JSON string. In objects it silently drops keys whose value is a function, undefined, or a symbol; in arrays those become null. NaN and Infinity serialize to null. A top-level undefined or function yields undefined (no string), not the text 'undefined'.
Common mistakes
- ✗Expecting
JSON.stringifyto throw onundefinedor functions instead of dropping them - ✗Thinking
NaNandInfinitysurvive as numbers rather than becomingnull - ✗Assuming a top-level
undefinedreturns the string'undefined'rather thanundefined
Follow-up questions
- →Why does an array hole or array
undefinedbecomenullwhile an object key is dropped? - →How can a
replacerfunction change which values get serialized?
MiddleTheoryCommonHow does the JSON.parse(JSON.stringify(x)) clone work, and what are its pitfalls?
How does the JSON.parse(JSON.stringify(x)) clone work, and what are its pitfalls?
Serializing to JSON and parsing back produces a deep copy with no shared references for plain data. But it only round-trips JSON-representable values: functions, undefined, and symbols are dropped, Date becomes a string, Map/Set become {}, NaN/Infinity become null, BigInt throws, and a circular reference throws a TypeError.
Common mistakes
- ✗Calling it a shallow copy — it deep-copies all JSON-representable nested data
- ✗Assuming
Date,Map,Set, or functions survive the round-trip intact - ✗Forgetting a circular reference makes
JSON.stringifythrow aTypeError
Follow-up questions
- →Which built-in API deep-clones cycles and
Dateobjects that the JSON trick cannot? - →Why does a
Datecome back as a string rather than aDateafter the round-trip?
JuniorTheoryOccasionalWhy is eval considered dangerous, and what should you use instead?
Why is eval considered dangerous, and what should you use instead?
eval runs an arbitrary string as code with the caller's privileges, so untrusted input becomes a code-injection hole. It also defeats engine optimization, can read and write surrounding scope, and makes code hard to reason about. To turn text into data use JSON.parse; to look up dynamic keys use bracket notation instead.
Common mistakes
- ✗Believing
evalruns in a sandbox rather than with the caller's full scope and privileges - ✗Using
evalto parse data whenJSON.parseis the safe, purpose-built tool - ✗Reaching for
evalto read a dynamic property instead of bracket notation
Follow-up questions
- →How does
evalaccess to surrounding scope block engine optimizations? - →When does
JSON.parsefail to replace anevalcall, and what does that imply about the input?
MiddleTheoryOccasionalWhat JavaScript values can JSON not represent, and how does each fail?
What JavaScript values can JSON not represent, and how does each fail?
JSON cannot encode several JavaScript values. A circular reference makes JSON.stringify throw a TypeError, and so does a BigInt. undefined, functions, and symbols are dropped as object keys and become null in arrays. NaN and Infinity serialize to null. Date has no native type and is stringified via its toJSON.
Common mistakes
- ✗Expecting a circular reference to be handled gracefully instead of throwing
- ✗Assuming
BigIntserializes to a number rather than throwing aTypeError - ✗Thinking
NaNandInfinityare kept as numbers rather than becomingnull
Follow-up questions
- →How can you serialize a
BigIntdespiteJSON.stringifythrowing on it by default? - →Why does a circular reference throw while a deeply nested but acyclic object serializes fine?
MiddleTheoryOccasionalHow do the replacer, toJSON(), and indent arguments shape JSON.stringify output?
How do the replacer, toJSON(), and indent arguments shape JSON.stringify output?
JSON.stringify(value, replacer, space) lets you customize output. A replacer function transforms or drops each key/value pair; a replacer array whitelists which keys survive. If a value has a toJSON() method, its return value is serialized instead. The third space argument (a number or string) pretty-prints with that indentation.
Common mistakes
- ✗Thinking a
replacerarray sets defaults rather than whitelisting keys to keep - ✗Believing a
replacerfunction must return a boolean instead of a transformed value - ✗Forgetting
toJSON()lets a value control its own serialized form (asDatedoes)
Follow-up questions
- →In what order does the engine apply
toJSON()and areplacerto the same value? - →How would you use a
replacerfunction to redact sensitive keys before logging?
SeniorTheoryOccasionalHow do structuredClone, the JSON round-trip, and a manual clone differ in what they handle?
How do structuredClone, the JSON round-trip, and a manual clone differ in what they handle?
structuredClone deep-copies circular graphs, Date, RegExp, Map, Set, and typed arrays, but throws on functions and DOM nodes. The JSON round-trip is simpler yet lossy: it drops functions, breaks on cycles and BigInt, and degrades Date/Map. A manual clone handles anything — including functions — but you must write and maintain that logic yourself.
Common mistakes
- ✗Believing
structuredClonebehaves like the JSON round-trip and losesDate/Map - ✗Assuming
structuredClonecan clone functions or DOM nodes — it throws on both - ✗Thinking the JSON round-trip can copy a cycle, when it throws a
TypeError
Follow-up questions
- →Why can
structuredClonecopy a circular graph thatJSON.stringifyrejects? - →When is a hand-written clone still necessary despite
structuredCloneexisting?
MiddleTheoryRareWhat is the reviver argument of JSON.parse, and what is it used for?
What is the reviver argument of JSON.parse, and what is it used for?
JSON.parse(text, reviver) passes a function that is called for every key/value pair, bottom-up, after parsing. Its return value replaces the parsed value; returning undefined deletes that key. It is typically used to transform raw values back into rich types — for example turning ISO date strings into real Date objects during parsing.
Common mistakes
- ✗Thinking the
reviverruns once on the root rather than for every nested pair - ✗Believing it preprocesses the text instead of transforming already-parsed values
- ✗Not knowing that returning
undefinedfrom thereviverdeletes that key
Follow-up questions
- →Why does the
revivervisit values bottom-up (children before their parent)? - →How do
reviverand a stringify-sidetoJSON()combine for a full round-trip of aDate?
SeniorTheoryRareHow do you design a toJSON/reviver round-trip that preserves rich types like dates and maps?
How do you design a toJSON/reviver round-trip that preserves rich types like dates and maps?
Define a tagged schema: on serialization, a toJSON or a replacer converts each rich value into a plain JSON object carrying a type marker and a serializable payload, e.g. { __type: 'Map', value: [...] }. On parse, a reviver matches that marker and reconstructs the original type. The marker convention is the contract both sides share to survive the lossy JSON layer.
Common mistakes
- ✗Expecting
JSON.stringifyto retain constructors without an explicit type marker - ✗Putting
toJSONandreviveron the wrong sides of the round-trip - ✗Encoding rich values without a tag, leaving the
reviverunable to know the original type
Follow-up questions
- →How would your schema handle nested rich types, such as a
Mapwhose values areDateobjects? - →What collision risk does a
__typemarker carry, and how do you guard real data against it?