| | |

JSON 05 ๐Ÿ“„ Parsing JSON and Stringifying Data in JavaScript/Node.js

โฑ๏ธ Estimated read: 15 min | |

The JSON object is the boundary between the text and the in-memory data in JavaScript. The JSON.parse() method takes a JSON string and returns the JavaScript value, and the JSON.stringify() method takes a JavaScript value and returns the JSON string . The two methods are the built-in serialization mechanism, and they are the most frequently used APIs in the web development. The surface is smallโ€”two functions and a handful of parametersโ€”but the behavior at the edges is where the bugs live. The circular references throw, the Date objects serialize through the toJSON() hook, the undefined and the functions are dropped, the NaN becomes null, and the large integers are rounded before the reviver is even called.

The JSON.parse() accepts the text and the optional reviver. The reviver is called for every key-value pair, bottom-up, and its return value replaces the value in the result. The reviver is the transformation hook, and it is also the security surface: the Axios CVE-2026-42044 showed that a polluted Object.prototype.parseReviver is invoked for every JSON response, and the attacker can silently escalate a role: "user" to role: "admin" without any error . The JSON.stringify() accepts the value, the optional replacer, and the optional space. The replacer can filter or transform the properties, and the space controls the indentation. The toJSON() method on an object is called before the serialization, which is how the Date becomes an ISO string .

Key point: JSON.parse(text, reviver) parses the text and applies the reviver bottom-up. JSON.stringify(value, replacer, space) serializes the value and applies the replacer. The reviver is a security surface, and the large integers are rounded before the reviver sees them. The circular references throw, and the toJSON() hook is the custom serialization mechanism.


Why the JavaScript implementation has the edges

The single-thread problem. Node.js runs the application on a single main thread, and that thread also runs the event loop. When the code calls JSON.stringify() or JSON.parse() on a large object, the work runs to completion on the same thread. While the serialization or the parsing is running, the new requests are accepted by the operating system but the JavaScript cannot handle them yet. The timers fire late, the health checks time out, and the event loop lag climbs into the hundreds of milliseconds . The edge is the synchronous blocking, and the fix is to move the large payloads off the main thread or to use the streaming parsers.

The circular reference problem. A JavaScript object can contain a reference to itself, directly or through a chain. The JSON.stringify() cannot represent the cycle, and it throws the TypeError: Converting circular structure to JSON . The fix is the WeakSet in the replacer, which records the objects that have been visited and returns a marker string for the ones that have already been seen. The WeakSet does not prevent the garbage collection, which is the reason it is the correct data structure for the one-time serialization .

The large integer problem. The JSON.parse() converts every number to the IEEE-754 double, and the integers above Number.MAX_SAFE_INTEGER (2^53 – 1) are rounded. The 9453860324986718414640 becomes 9453860324986719305728, which is off by 891,088 . The reviver cannot recover the lost precision because the value it receives is already the rounded double. The fix is to serialize the large integers as strings, or to use the parser that preserves the precision, such as the @decentralchain/parse-json-bignumber library, which returns the numbers with more than 15 digits as strings by default .

The toJSON priority problem. The JSON.stringify() calls the toJSON() method on the object before the serialization, and the return value of the toJSON() is what the replacer receives. The Date object has the toJSON() that returns the ISO string, which is why the date serializes as a string . The custom toJSON() is the mechanism for the custom serialization, but it also means the replacer cannot see the original objectโ€”it sees the toJSON() result.

The eval comparison problem. The JSON.parse() is the safe alternative to the eval(). The eval() executes the text as JavaScript, and a malicious text can contain the arbitrary code. The JSON.parse() validates the grammar and rejects the text that is not valid JSON . The distinction is the reason the JSON.parse() is the standard, and the eval() is the anti-pattern.


a. The JSON.parse() and the reviver mechanics

The JSON.parse() signature is JSON.parse(text, reviver). The text is the string, and the reviver is the optional function. The function receives the key and the value, and the return value is the value that the result uses. The function is called bottom-up: the leaf values are processed first, and the container values are processed after their children .

// The basic parse
const data = JSON.parse('{"name":"Ada","age":36}');
// { name: 'Ada', age: 36 }

// The reviver transforms the values
const parsed = JSON.parse('{"name":"Ada","age":36}', (key, value) => {
  // The function is called for every key-value pair.
  // The key is the property name, the value is the parsed value.
  if (key === 'age') return value * 2;
  return value;
});
// { name: 'Ada', age: 72 }

The bottom-up order is the mechanism that allows the reviver to transform the children before the parent. The parent’s value is the object with the transformed children, and the reviver can inspect the transformed children.

// The bottom-up order
const parsed = JSON.parse('{"a":{"b":1}}', (key, value) => {
  console.log(key, value);
  return value;
});
// b 1
// a { b: 1 }
// '' { a: { b: 1 } }

// The root key is the empty string.
// The root value is the whole parsed object.

The root call has the empty string as the key and the whole parsed value as the value. The reviver can return a different value for the root, and the JSON.parse() returns the transformed root .


b. The JSON.stringify() and the replacer mechanics

The JSON.stringify() signature is JSON.stringify(value, replacer, space). The value is the object, the replacer is the optional function or array, and the space is the optional indentation . The replacer function receives the key and the value, and the return value is the value that the serialization uses. Returning undefined omits the property from the output .

// The basic stringify
const json = JSON.stringify({ name: "Ada", age: 36 });
// '{"name":"Ada","age":36}'

// The replacer function
const filtered = JSON.stringify({ name: "Ada", password: "x" }, (key, value) => {
  // Returning undefined omits the property.
  if (key === 'password') return undefined;
  return value;
});
// '{"name":"Ada"}'

// The space parameter
const pretty = JSON.stringify({ name: "Ada" }, null, 2);
// '{\n  "name": "Ada"\n}'

The replacer array is the whitelist form. It only keeps the specified keys, and the filtering is recursive for the nested objects . The replacer function is the flexible form, and it can inspect every key-value pair.

The toJSON() method is the custom serialization hook. The JSON.stringify() calls it before the serialization, and the return value is what the replacer receives. The Date object uses it to serialize as the ISO string, and a custom class can use it to control its own serialization .

// The toJSON hook
class User {
  constructor(name) {
    this.name = name;
  }
  toJSON() {
    // The return value is what the serialization uses.
    return { userName: this.name, type: 'user' };
  }
}

JSON.stringify(new User('Ada'));
// '{"userName":"Ada","type":"user"}'

c. The security and the performance edges

The security edge is the reviver. The JSON.parse(text, this.parseReviver) call is the pattern that the Axios library used, and the this.parseReviver walks the prototype chain. The polluted Object.prototype.parseReviver is found, and the attacker-controlled function is called for every key-value pair in every JSON response . The fix is the own-property check: the reviver must be an explicit function, not a function inherited from the prototype chain.

// The unsafe pattern (the Axios CVE)
// const data = JSON.parse(text, this.parseReviver);
// The this.parseReviver may be inherited from the prototype chain.

// The safe pattern
function safeParse(text, reviver) {
  // The reviver must be an explicit function.
  if (typeof reviver !== 'function') {
    return JSON.parse(text);
  }
  return JSON.parse(text, reviver);
}

The performance edge is the synchronous blocking. The JSON.stringify() and the JSON.parse() run to completion on the main thread, and the large payloads block the event loop . The fix is to move the large payloads off the main thread, to use the streaming parsers, or to split the work into the smaller chunks.

// The safe stringify with the circular reference handling
function safeStringify(obj, space = 2) {
  const seen = new WeakSet();
  return JSON.stringify(obj, (key, value) => {
    if (typeof value === 'object' && value !== null) {
      if (seen.has(value)) {
        return '[Circular]';
      }
      seen.add(value);
    }
    return value;
  }, space);
}

The WeakSet records the objects that have been visited, and the circular reference is replaced with the [Circular] marker instead of throwing . The safeStringify is the pattern for the logging and the debugging, where the circular references are common and the serialization must not crash the application.


Complete Example Session

// ============================================
// PART 1: THE BASIC PARSE
// ============================================

const data = JSON.parse('{"name":"Ada","age":36}');
// { name: 'Ada', age: 36 }

// The text is validated against the grammar.
// The valid JSON is parsed, the invalid is rejected.

// ============================================
// PART 2: THE REVIVER
// ============================================

const parsed = JSON.parse('{"name":"Ada","age":36}', (key, value) => {
  if (key === 'age') return value * 2;
  return value;
});
// { name: 'Ada', age: 72 }

// The reviver is called for every key-value pair.
// The return value replaces the value.

// ============================================
// PART 3: THE BOTTOM-UP ORDER
// ============================================

JSON.parse('{"a":{"b":1}}', (key, value) => {
  console.log(key, value);
  return value;
});
// b 1
// a { b: 1 }
// '' { a: { b: 1 } }

// The leaf is processed first.
// The root is processed last.

// ============================================
// PART 4: THE PROTOTYPE POLLUTION
// ============================================

// The polluted reviver
Object.prototype.parseReviver = (key, value) => {
  if (key === 'role') return 'admin';
  return value;
};

const response = JSON.parse('{"role":"user","balance":100}');
// { role: 'admin', balance: 100 } (the role was escalated)

delete Object.prototype.parseReviver;

// The reviver came from the prototype chain.
// The data was silently modified.

// ============================================
// PART 5: THE SAFE PATTERN
// ============================================

function safeParse(text, reviver) {
  if (typeof reviver !== 'function') {
    return JSON.parse(text);
  }
  return JSON.parse(text, reviver);
}

// The reviver must be the explicit function.
// The inherited reviver is not used.

// ============================================
// PART 6: THE LARGE INTEGER
// ============================================

const big = JSON.parse('{"id":9453860324986718414640}');
console.log(big.id); // 9453860324986719305728 (rounded)

// The precision is lost before the reviver is called.
// The double has 53 bits of integer precision.

// ============================================
// PART 7: THE STRING WORKAROUND
// ============================================

const safeBig = JSON.parse('{"id":"9453860324986718414640"}');
console.log(safeBig.id); // "9453860324986718414640" (exact)

// The large integer is serialized as a string.
// The string preserves the exact digits.

// ============================================
// PART 8: THE TOJSON HOOK
// ============================================

class User {
  constructor(name) {
    this.name = name;
  }
  toJSON() {
    return { userName: this.name, type: 'user' };
  }
}

JSON.stringify(new User('Ada'));
// '{"userName":"Ada","type":"user"}'

// The toJSON is called before the serialization.
// The return value is what the serialization uses.

// ============================================
// PART 9: THE CIRCULAR REFERENCE
// ============================================

const user = { name: 'Ada', profile: {} };
user.profile.owner = user;

try {
  JSON.stringify(user);
} catch (e) {
  // TypeError: Converting circular structure to JSON
}

// The circular reference throws.
// The fix is the WeakSet in the replacer.

// ============================================
// PART 10: THE CHECKLIST
// ============================================

// [ ] The JSON.parse is used, not the eval.
// [ ] The reviver is the explicit function, not the inherited one.
// [ ] The large integers are serialized as strings.
// [ ] The toJSON hook is used for the custom serialization.
// [ ] The circular references are handled with the WeakSet.
// [ ] The large payloads are moved off the main thread.

The ten parts covered the basic parse, the reviver, the bottom-up order, the prototype pollution, the safe pattern, the large integer, the string workaround, the toJSON hook, the circular reference, and the checklist. The through-line is that the JSON.parse() and JSON.stringify() are the boundary, and the edges are the security and the precision.


Quick Reference

The JSON.parse() signature

ParameterTypePurpose
textstringThe JSON text to parse
reviverfunctionThe optional transformation hook
ReturnanyThe parsed value

The JSON.stringify() signature

ParameterTypePurpose
valueanyThe value to serialize
replacerfunction | arrayThe optional filter or transform
spacenumber | stringThe optional indentation
ReturnstringThe JSON text

The edge cases

CaseBehavior
Circular referenceThrows TypeError
DateSerialized via toJSON() as ISO string
undefinedOmitted from the output
NaN / InfinityConverted to null
Large integerRounded to the nearest double
BigIntThrows TypeError

The security patterns

PatternSafe?Reason
JSON.parse(text)YesNo reviver, no attack surface
JSON.parse(text, explicitReviver)YesThe reviver is the explicit function
JSON.parse(text, this.reviver)NoThe reviver may be inherited
eval('(' + text + ')')NoThe text is executed

Best Practices

โœ… Do This:

// Use JSON.parse, not eval
const data = JSON.parse(text);                                    // โœ…

// Use the explicit reviver function
const parsed = JSON.parse(text, (key, value) => value);          // โœ…

// Serialize the large integers as strings
const safe = JSON.stringify({ id: "9453860324986718414640" });   // โœ…

// Use the WeakSet for the circular references
const seen = new WeakSet();                                       // โœ…

โŒ Don’t Do This:

// Use eval to parse
const data = eval('(' + text + ')');                              // โŒ

// Use the inherited reviver
const parsed = JSON.parse(text, this.parseReviver);               // โŒ 

// Assume the large integers are exact
JSON.parse('{"id":9453860324986718414640}');                      // โŒ

Common Pitfalls

PitfallWhy It HappensFix
The role is escalatedThe reviver is inherited from the prototypeUse the own-property check
The large integer is roundedThe IEEE-754 double precisionSerialize as a string
The serialization throwsThe circular referenceUse the WeakSet in the replacer
The event loop is blockedThe large payload on the main threadMove the work off the main thread
The date is a stringThe toJSON() hookConvert back to the Date after the parse

Real-World Examples

1. The basic parse

JSON.parse('{"name":"Ada"}');

2. The reviver

JSON.parse('{"age":36}', (k, v) => k === 'age' ? v * 2 : v);

3. The bottom-up order

JSON.parse('{"a":{"b":1}}', (k, v) => { console.log(k); return v; });

4. The prototype pollution

Object.prototype.parseReviver = (k, v) => k === 'role' ? 'admin' : v;

5. The large integer

JSON.parse('{"id":9453860324986718414640}').id; // rounded

6. The string workaround

JSON.parse('{"id":"9453860324986718414640"}').id; // exact

7. The toJSON hook

class User { toJSON() { return { name: 'Ada' }; } }

8. The circular reference

const seen = new WeakSet();
JSON.stringify(obj, (k, v) => seen.has(v) ? '[Circular]' : v);

9. The safe parse

function safeParse(text, reviver) { return JSON.parse(text, typeof reviver === 'function' ? reviver : undefined); }

10. The event loop blocking

// The large payload on the main thread blocks the event loop.

Visual

The Parse and the Reviver

โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚  THE PARSE PIPELINE                                          โ”‚
โ”‚                                                              โ”‚
โ”‚  JSON text                                                   โ”‚
โ”‚  '{"name":"Ada","age":36}'                                   โ”‚
โ”‚         โ”‚                                                    โ”‚
โ”‚         โ–ผ                                                    โ”‚
โ”‚  โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”                                    โ”‚
โ”‚  โ”‚ The tokenizer       โ”‚  Validates the grammar             โ”‚
โ”‚  โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜                                    โ”‚
โ”‚             โ”‚                                                โ”‚
โ”‚             โ–ผ                                                    โ”‚
โ”‚  โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”                                    โ”‚
โ”‚  โ”‚ The parser          โ”‚  Constructs the values             โ”‚
โ”‚  โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜                                    โ”‚
โ”‚             โ”‚                                                โ”‚
โ”‚             โ–ผ                                                    โ”‚
โ”‚  โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”                                    โ”‚
โ”‚  โ”‚ The reviver         โ”‚  Transforms the values             โ”‚
โ”‚  โ”‚ (bottom-up)         โ”‚                                    โ”‚
โ”‚  โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜                                    โ”‚
โ”‚             โ”‚                                                โ”‚
โ”‚             โ–ผ                                                    โ”‚
โ”‚  โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”                                    โ”‚
โ”‚  โ”‚ { name: "Ada",      โ”‚  The result                        โ”‚
โ”‚  โ”‚   age: 36 }         โ”‚                                    โ”‚
โ”‚  โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜                                    โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜

The Prototype Pollution

โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚  THE INHERITED REVIVER                                       โ”‚
โ”‚                                                              โ”‚
โ”‚  Object.prototype                                            โ”‚
โ”‚  โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”                                    โ”‚
โ”‚  โ”‚ parseReviver: fn    โ”‚  The polluted function             โ”‚
โ”‚  โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜                                    โ”‚
โ”‚             โ”‚                                                โ”‚
โ”‚             โ”‚ inherited by                                   โ”‚
โ”‚             โ–ผ                                                โ”‚
โ”‚  โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”                                    โ”‚
โ”‚  โ”‚ config              โ”‚  The Axios config object           โ”‚
โ”‚  โ”‚ (no own parseReviverโ”‚                                    โ”‚
โ”‚  โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜                                    โ”‚
โ”‚             โ”‚                                                โ”‚
โ”‚             โ–ผ                                                    โ”‚
โ”‚  โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”                                    โ”‚
โ”‚  โ”‚ JSON.parse(text,    โ”‚  The inherited reviver is used     โ”‚
โ”‚  โ”‚   this.parseReviver)โ”‚                                    โ”‚
โ”‚  โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜                                    โ”‚
โ”‚             โ”‚                                                โ”‚
โ”‚             โ–ผ                                                    โ”‚
โ”‚  โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”                                    โ”‚
โ”‚  โ”‚ role: "admin"       โ”‚  The data is silently modified     โ”‚
โ”‚  โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜                                    โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜

The Large Integer Precision

โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚  THE DOUBLE PRECISION                                        โ”‚
โ”‚                                                              โ”‚
โ”‚  The text:                                                   โ”‚
โ”‚  {"id":9453860324986718414640}                               โ”‚
โ”‚         โ”‚                                                    โ”‚
โ”‚         โ–ผ                                                    โ”‚
โ”‚  โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”                                    โ”‚
โ”‚  โ”‚ JSON.parse          โ”‚  The number is the double          โ”‚
โ”‚  โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜                                    โ”‚
โ”‚             โ”‚                                                โ”‚
โ”‚             โ–ผ                                                    โ”‚
โ”‚  โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”                                    โ”‚
โ”‚  โ”‚ 9453860324986719305728โ”‚  The precision is lost           โ”‚
โ”‚  โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜                                    โ”‚
โ”‚                                                              โ”‚
โ”‚  The fix: serialize as a string or use a lossless parser.   โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜

Summary

ItemValue
JSON.parseThe safe parser, no eval
reviverThe transformation hook, called bottom-up
The root callThe key is '', the value is the whole object
The prototype pollutionThe inherited reviver can modify the data
The large integerThe IEEE-754 double rounds above 2^53
The safe patternThe own-property check for the reviver
The circular referenceThrows TypeError, fix with the WeakSet
The toJSON hookThe custom serialization mechanism

Key takeaways:

  • JSON.parse is the safe alternative to eval. The parser validates the grammar and rejects the text that is not valid JSON, while the eval executes the text as JavaScript . The JSON.parse does not execute the text; it parses it.
  • The reviver is the transformation hook and the security surface. The function is called for every key-value pair, bottom-up, and the return value replaces the value . The polluted Object.prototype.parseReviver is invoked for every JSON response, and the Axios CVE-2026-42044 is the concrete example: the role: "user" was silently escalated to role: "admin" .
  • The large integers are rounded before the reviver is called. The JSON.parse converts every number to the IEEE-754 double, and the integers above 2^53 are rounded . The fix is to serialize the large integers as strings, or to use the parser that preserves the precision .
  • The JSON.stringify has the toJSON hook and the circular reference edge. The toJSON() method is called before the serialization, which is how the Date becomes an ISO string . The circular reference throws, and the fix is the WeakSet in the replacer .
  • The synchronous blocking is the Node.js performance edge. The JSON.stringify() and the JSON.parse() run to completion on the main thread, and the large payloads block the event loop . The fix is to move the work off the main thread or to use the streaming parsers.

Remember: The JSON.parse() and JSON.stringify() are the boundary between the text and the data. The JSON.parse() is the safe parser, and the reviver is the transformation hook and the security surface. The JSON.stringify() has the toJSON hook and the replacer, and the circular reference is the edge. The large integers are the precision hazard, and the synchronous blocking is the performance edge. The two methods are the most used APIs in the JavaScript, and the edges are where the bugs live.

Was this helpful?


Stop using slow, ad-bloated tool sites! ๐Ÿคฎ

๐Ÿ”Ž Search “KandZ Tools” on Google to use many professional utilities for free.

KandZ.me is the ultimate minimalist hub for:
โœ… Finance (Mortgage, Interest, Inflation)
โœ… Tech (Base64, JSON, Dev Suite, IP)
โœ… Health (BMI, BMR, TDEE)
โœ… Productivity (Timer, Workspace, QR)

โšก๏ธ Fast & Private
๐Ÿ”’ No data leaves your device
๐Ÿ’Ž 100% Free

๐Ÿ”— Use it now: https://tools.kandz.me
๐Ÿ”– Bookmark itโ€”youโ€™ll need it later!