Skip to content

feat: support reviver parameter and fix precision parsing for huge numbers - #162

Open
cf3901646 wants to merge 2 commits into
unjs:mainfrom
cf3901646:feat/reviver-and-huge-numbers
Open

cf3901646 wants to merge 2 commits into
unjs:mainfrom
cf3901646:feat/reviver-and-huge-numbers

Conversation

@cf3901646

@cf3901646 cf3901646 commented May 22, 2026

Copy link
Copy Markdown

📝 Description

This PR brings two significant enhancements and bug fixes to destr, ensuring better parity with native JSON.parse and improving robustness when parsing huge numbers.

1. Support for reviver Parameter (JSON.parse parity)

  • Feature: Extends the signatures of destr and safeDestr to accept a custom reviver function, either nested in the options object or passed directly as the second argument (seamless drop-in replacement for native JSON.parse(text, reviver)).
  • Security-First Chaining: When a prototype-pollution signature is detected, the pollution filter drops the unsafe keys (__proto__, constructor) first, and safely chains the remaining sanitized keys into the user's custom reviver. On normal paths, it directly utilizes native JSON.parse(value, reviver) for optimal performance.

2. Fix for Huge Numbers Being Treated as Invalid JSON

  • Bug: The internal JsonSigRx regular expression was hardcoded to expect digits with max length 16 (\d{1,16}). This caused long numeric strings (e.g., "123456789012345678901234567890") to fail validation. It fell back to returning raw strings in default mode, and incorrectly threw SyntaxError: [destr] Invalid JSON in strict (safeDestr) mode.
  • Fix: Relaxed the rigid digit-length limits in JsonSigRx since native JSON.parse handles numeric parsing natively (even with IEEE 754 precision rounding), bringing full compliance to specification behaviors without losing error safety.

🧪 Tests & Verification

  • Added new robust unit tests in test/index.test.ts covering:
    • Big numbers parsing in both destr and safeDestr.
    • reviver options payload parsing.
    • Direct reviver argument parsing.
    • Correct execution order between prototype-pollution prevention filter and custom reviver callbacks.
  • All 26 unit tests passed successfully:
    ✓ test/index.test.ts (26 tests) 13ms
  • Code formatted and lint-checked via npm run lint.

Summary by CodeRabbit

  • New Features

    • Added optional reviver callback for custom transformation of JSON values during parsing
    • API now accepts a reviver function as an alternative to the options object
  • Bug Fixes

    • Improved prototype-pollution protection to filter dangerous properties during parse
  • Tests

    • Expanded tests for reviver behavior, very large numeric strings, and security protections

@coderabbitai

coderabbitai Bot commented May 22, 2026

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 6c54d534-4207-474b-9ee4-8e694712a81b

📥 Commits

Reviewing files that changed from the base of the PR and between 399f304 and 662c293.

📒 Files selected for processing (2)
  • src/index.ts
  • test/index.test.ts
🚧 Files skipped from review as they are similar to previous changes (2)
  • test/index.test.ts
  • src/index.ts

📝 Walkthrough

Walkthrough

The PR extends destr's JSON parsing API with optional reviver callback support, unifies parameter handling to accept either Options or a reviver function, and replaces the separate transform-based prototype pollution protection with an inline reviver that filters dangerous keys before delegating to user-provided callbacks.

Changes

Reviver and Security Filtering

Layer / File(s) Summary
Options type and function signatures
src/index.ts
Extended Options with optional reviver field; generalized destr and safeDestr signatures to accept either Options object or a reviver function.
Core destr implementation with reviver and security filtering
src/index.ts
Implemented inline JSON.parse reviver that blocks __proto__ and constructor.prototype vectors before delegating to options.reviver; direct reviver delegation for non-suspect inputs replaces separate transform path.
safeDestr parameter handling and reviver support
src/index.ts
Updated safeDestr to normalize optionsOrReviver union into options object and enforce strict: true when delegating to destr.
Test coverage for reviver functionality and security
test/index.test.ts
Added tests for large numeric string handling, reviver support via options and direct argument form, and verification that prototype pollution filtering precedes user reviver invocation.
sequenceDiagram
  participant Caller
  participant destr
  participant JSONparse
  participant optionsReviver
  Caller->>destr: destr(value, optionsOrReviver)
  destr->>destr: normalize options / extract reviver
  alt suspect JSON (proto-pollution)
    destr->>JSONparse: JSON.parse(value, inlineReviver)
    JSONparse->>destr: inlineReviver drops __proto__/constructor.prototype
    destr->>optionsReviver: call options.reviver(this,key,value) when applicable
  else non-suspect
    destr->>JSONparse: JSON.parse(value, options.reviver)
  end
  JSONparse-->>Caller: parsed result
Loading

🎯 4 (Complex) | ⏱️ ~45 minutes

🐰 A reviver hops into the fold,
With prototype guards to be bold,
No __proto__ sneaks through—
The parser stays true,
And user hooks get stories untold!

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The PR title clearly describes the two main changes: adding reviver parameter support and fixing large number parsing precision.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (2)
test/index.test.ts (2)

201-207: ⚡ Quick win

Strengthen huge-number test with value parity, not only type checks.

typeof === "number" can still pass on incorrect numeric results. Assert the parsed value explicitly to protect this regression path.

Suggested test update
   it("parses huge numbers without throwing in safeDestr", () => {
     const hugeNumber = "123456789012345678901234567890";
-    // 默认模式下应该解析为数字类型(即便有精度损失,也应符合原生 JSON.parse 行为)
-    expect(typeof destr(hugeNumber)).toBe("number");
-    // 严格模式下不应抛出 "Invalid JSON" 错误
-    expect(typeof safeDestr(hugeNumber)).toBe("number");
+    const expected = JSON.parse(hugeNumber);
+    expect(destr(hugeNumber)).toBe(expected);
+    expect(safeDestr(hugeNumber)).toBe(expected);
   });
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@test/index.test.ts` around lines 201 - 207, The test currently only checks
typeof for destr and safeDestr with a huge numeric string; update it to assert
value parity as well (not just type) so the parsed result matches JavaScript's
numeric conversion behavior. In the "parses huge numbers without throwing in
safeDestr" test, after the typeof assertions for destr and safeDestr, add
explicit assertions that destr(hugeNumber) and safeDestr(hugeNumber) equal
Number(hugeNumber) (or the expected numeric value) to ensure both functions
return the same numeric value as native conversion; reference the test name and
the functions destr and safeDestr when locating where to change.

220-227: ⚡ Quick win

Add direct-argument reviver coverage for safeDestr too.

The overload changed for both parsers, but this test only validates direct reviver invocation on destr.

Suggested test addition
     it("supports reviver as the second argument directly", () => {
       const input = '{"a": 1, "b": 2}';
       const reviver = (key: string, value: any) => {
         if (key === "a") return value * 10;
         return value;
       };
       expect(destr(input, reviver)).toStrictEqual({ a: 10, b: 2 });
+      expect(safeDestr(input, reviver)).toStrictEqual({ a: 10, b: 2 });
     });
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@test/index.test.ts` around lines 220 - 227, Add a parallel test that verifies
safeDestr accepts a reviver as the second argument just like destr: copy the
existing "supports reviver as the second argument directly" test (which defines
input '{"a": 1, "b": 2}' and reviver that multiplies "a" by 10) and create a new
it block for safeDestr that calls safeDestr(input, reviver) and expects { a: 10,
b: 2 }; ensure the new test name clearly references safeDestr so both overloads
are covered.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/index.ts`:
- Around line 80-95: The custom filtered reviver passed to JSON.parse currently
uses an arrow function and calls options.reviver(key, val), losing the correct
`this` binding; change the wrapper to a normal function (function(key, val) {
... }) and invoke the user reviver with the holder `this` (e.g.
options.reviver.call(this, key, val)) so behavior matches native JSON.parse;
keep the existing prototype/constructor filtering and warnKeyDropped calls
intact.

---

Nitpick comments:
In `@test/index.test.ts`:
- Around line 201-207: The test currently only checks typeof for destr and
safeDestr with a huge numeric string; update it to assert value parity as well
(not just type) so the parsed result matches JavaScript's numeric conversion
behavior. In the "parses huge numbers without throwing in safeDestr" test, after
the typeof assertions for destr and safeDestr, add explicit assertions that
destr(hugeNumber) and safeDestr(hugeNumber) equal Number(hugeNumber) (or the
expected numeric value) to ensure both functions return the same numeric value
as native conversion; reference the test name and the functions destr and
safeDestr when locating where to change.
- Around line 220-227: Add a parallel test that verifies safeDestr accepts a
reviver as the second argument just like destr: copy the existing "supports
reviver as the second argument directly" test (which defines input '{"a": 1,
"b": 2}' and reviver that multiplies "a" by 10) and create a new it block for
safeDestr that calls safeDestr(input, reviver) and expects { a: 10, b: 2 };
ensure the new test name clearly references safeDestr so both overloads are
covered.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: bae2c857-fa0c-42d4-bff5-044b3aceb40b

📥 Commits

Reviewing files that changed from the base of the PR and between 541b6f9 and 399f304.

📒 Files selected for processing (2)
  • src/index.ts
  • test/index.test.ts

Comment thread src/index.ts Outdated
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant