Technology · PRACTICAL GUIDE
jq preserves the ID until your filter runs? Test the real handoff before adding BigInt
An identity filter can pass while arithmetic changes the last digit. Use paired raw-output controls to locate the loss before a downstream reviver makes it look exact.

When an ID changes its last digit, save each raw handoff before replacing the parser. A successful identity test does not establish that the actual arithmetic filter preserves every digit.
Eight local cases separate the source text, jq output and Node parsing boundary. String-based expectations check the complete digits instead of comparing two already rounded Numbers.
1. Keep the expected ID outside Number
Start with the literal JSON text {"id":9007199254740993,"count":42} and keep the expected ID as the string "9007199254740993". Do not write it as a JavaScript numeric literal and compare two Numbers: the expected value can already be rounded. Record the exact jq and Node executables, versions and production filter before changing the payload.
RFC8259 permits implementation limits and identifies the interoperable integer range through 2^53−1 in magnitude. Outside that range, some integers remain representable; not every large value changes. An opaque ID needs its exact digits, so a successful parse or a plausible-looking log line is not an acceptance check.
2. Pair the identity probe with the real filter
Our eight local Linux cases used jq1.7 and Nodev24.22.1. With the fixture above, jq -c '.' emitted the original ...993 token. The filter .id += 0 emitted ...992. The version1.7 manual documents retained unmutated literals separately from double-precision arithmetic. The identity pass therefore establishes only that particular operation, not the safety of every filter.
Capture the text emitted immediately before and after each transform. On this binary, .id |= tostring produced "9007199254740993"; .id += 0 | .id |= tostring produced "9007199254740992". Early string conversion and late string conversion are different controls. Repeat with your actual production filter and build; these results do not certify another jq implementation or release.
3. Prove what the reviver actually received
Ordinary JSON.parse followed by id.toString() yielded ...992. BigInt applied to that already parsed value also yielded ...992. A key-specific reviver reading BigInt(context.source) recovered ...993 directly from the original fixture and from the identity-filter output. After jq arithmetic, that same reviver yielded ...992: its source was already the changed token.
For a schema-known numeric ID, our test callback was (k,v,c)=>k==='id'&&typeof v==='number'?BigInt(c.source):v. It is a fixture callback, not a general validator. Probe context.source support in the actual runtime and validate the field path and integer-token grammar. Decimal or exponent forms need a separate contract; converting every numeric-looking string would also change fields that should remain text.
4. Close the full round trip
If you control the API, agree on a string ID with the receiver before migrating. Our eighth case explicitly serialized the recovered BigInt as id.toString(), parsed that string-contract payload and obtained all original digits. This proves the fixture round trip, not compatibility with an unchanged numeric schema. Avoid silently changing quantities, decimal amounts or unrelated fields.
Accept the repair only when the captured origin token, every intended handoff and the final schema-aware string comparison agree. Retain the real-filter regression beside the identity control, including ...993 versus ...992 and the first boundary that changed. Fixing the downstream reviver cannot restore upstream information. Our tests used local fixtures, not a live gateway or browser; rerun them at each real producer and consumer boundary.
