An external identifier can look like a number without being a quantity. You may need to place it in a URL, compare it with a provider response or store it in a ledger. None of those operations requires arithmetic.
Treating such an identifier as an ordinary JavaScript number can quietly change it before your duplicate check even runs. The resulting bug often appears somewhere else: a history lookup misses a post, a reconciliation step cannot find its receipt, or a URL points at the wrong resource.
The conversion can happen during parsing
JavaScript's Number type cannot represent every sufficiently large integer exactly. Converting an already-rounded value to a string does not restore the original digits.
You can demonstrate the boundary without calling any external API:
const assert = require('node:assert/strict');
const supplied = '831204016708108288';
const numeric = JSON.parse('{"id":' + supplied + '}');
assert.equal(Number.isSafeInteger(numeric.id), false);
const stringified = JSON.parse('{"id":"' + supplied + '"}');
assert.equal(stringified.id, supplied);
The first assertion does not tell you which resource was intended. It only tells you that the parsed value is outside the safe integer range. The second response shape preserves the identifier because JSON contains a string rather than a numeric token.
This distinction matters before the application reaches its usual validation code. The HTTP response can be complete and valid JSON while the chosen parsing representation is still unsuitable for an identifier.
Prefer the provider's string representation
If an API supplies a documented string identifier, use that field consistently. Some responses expose both numeric and string forms. Do not select the numeric form merely because its property name is shorter.
When a provider returns only a numeric token, choose a parsing strategy that preserves the original token before it becomes a Number. That may mean a lossless JSON parser appropriate to the response schema or a controlled tokenizer that keeps the relevant numeric lexeme as text.
A broad regular expression is not a general JSON parser. It can confuse quoted text, escaped characters or an unrelated field that happens to use the same name. If you use any narrow adapter for a known response shape, document the assumptions and test the actual edge cases.
A normal reviver is too late to repair lost digits
The familiar JSON.parse reviver receives values after the parser has constructed them. Calling String(value) or BigInt(value) on a number that has already lost precision preserves the rounded interpretation, not the original response token.
Similarly, checking that a value contains only digits after converting it to text does not establish correctness. It can be a perfectly well-formed identifier that belongs to the wrong resource.
Validation should distinguish representation from provenance. Knowing that a string contains decimal digits is useful. Knowing that those are the exact digits received from the provider is a separate requirement.
Keep IDs opaque at every boundary
Once an identifier is safely represented as a string, preserve that contract through storage and comparison. Watch for implicit conversions in database columns, CSV imports, analytics exports and helper functions that call Number on every numeric-looking value.
Avoid converting two identifiers to Number just to compare them. Compare their documented string representations instead. Do not strip leading zeroes unless the provider explicitly defines them as insignificant.
For a URL, encode the string for the intended URL component. Encoding protects the syntax of the URL; it does not validate the identifier or recover precision. These are separate concerns and should remain separate in the code review.
Test round trips, not just the happy path
Include a small identifier, one near the safe integer boundary and one beyond it. Add an identifier containing leading zeroes if the upstream schema permits that form. Test serialisation and deserialisation through the actual storage format you use.
For a publishing ledger, verify that the identifier from the write response remains unchanged when it is read back and used in a provider-history lookup. This connects the representation test to the failure that would matter operationally.
Also check error handling. If the provider response cannot be parsed without losing required information, stop before reporting a verified publication. Logging an uncertain receipt is more honest than manufacturing a reliable-looking identifier.
The Number.MAX_SAFE_INTEGER reference explains the numeric boundary. The practical design decision is simpler: an opaque external ID should stay an identifier throughout the system.
Top comments (0)