fix(read): preserve Int64 precision for unsafe Longs in tool outputs#1220
Open
STiFLeR7 wants to merge 2 commits into
Open
fix(read): preserve Int64 precision for unsafe Longs in tool outputs#1220STiFLeR7 wants to merge 2 commits into
STiFLeR7 wants to merge 2 commits into
Conversation
Introduce a custom EJSON stringifier helper that pre-processes objects to convert BSON Long values exceeding safe JavaScript integer limits into strict numberLong string representations, while keeping safe Long values as standard relaxed numbers. Use this custom stringifier across find, aggregate, aggregate-db, and db-stats tool outputs to prevent precision round-off errors in downstream LLM clients.
Contributor
There was a problem hiding this comment.
Pull request overview
Note
Copilot was unable to run its full agentic suite in this review.
Introduces a custom EJSON stringifier that preserves 64-bit integer precision by converting BSON Long values to standard JS numbers when within safe-integer range and to {$numberLong: "..."} otherwise, then wires it into the MongoDB read tools that previously used EJSON.stringify directly.
Changes:
- New
src/helpers/ejson.tswithserializeSafeLongsandstringifyEJSONhelpers, plus unit tests. - Replaced direct
EJSON.stringify(...)usage infind,aggregate,aggregateDB, anddbStatstools with the newstringifyEJSON.
Reviewed changes
Copilot reviewed 6 out of 6 changed files in this pull request and generated 2 comments.
Show a summary per file
| File | Description |
|---|---|
| src/helpers/ejson.ts | New helper that serializes safe Longs as numbers and unsafe Longs as $numberLong strict EJSON. |
| tests/unit/helpers/ejson.test.ts | Unit tests covering primitives, safe/unsafe Longs, and nested structures. |
| src/tools/mongodb/read/find.ts | Switched to stringifyEJSON for result serialization. |
| src/tools/mongodb/read/aggregate.ts | Switched to stringifyEJSON for result serialization. |
| src/tools/mongodb/read/aggregateDB.ts | Switched to stringifyEJSON for result serialization. |
| src/tools/mongodb/metadata/dbStats.ts | Switched to stringifyEJSON for stats serialization. |
Comment on lines
+17
to
+21
| const longObj = obj as unknown as Long; | ||
| const num = longObj.toNumber(); | ||
| if (num >= Number.MIN_SAFE_INTEGER && num <= Number.MAX_SAFE_INTEGER) { | ||
| return num; | ||
| } |
Comment on lines
+29
to
+38
| if (typeof obj === "object") { | ||
| const proto: unknown = Object.getPrototypeOf(obj); | ||
| if (proto === null || proto === Object.prototype) { | ||
| const result: Record<string, unknown> = {}; | ||
| for (const key of Object.keys(obj)) { | ||
| result[key] = serializeSafeLongs((obj as Record<string, unknown>)[key]); | ||
| } | ||
| return result; | ||
| } | ||
| } |
…mit check Compare the Long object directly rather than calling toNumber() first, preventing precision loss for boundary values (e.g. MAX_SAFE_INTEGER + 2) that round back to safe limits as floats. Add clarifying JSDoc documentation about the prototype recursion constraint.
lerouxb
reviewed
Jun 2, 2026
| replacer?: ((this: unknown, key: string, value: unknown) => unknown) | (string | number)[] | null, | ||
| space?: string | number | ||
| ): string { | ||
| return EJSON.stringify(serializeSafeLongs(value), replacer as Parameters<typeof EJSON.stringify>[1], space); |
Member
There was a problem hiding this comment.
Any reason to not just do EJSON.stringify(value, { relaxed: false })? Yes that gives you verbose EJSON always which we may or may not want, but then we don't need custom code at all.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Description
In relaxed mode serialization (
EJSON.stringifydefault), BSONLongvalues are directly serialized to standard JSON numbers. When these numbers exceed JavaScript's safe integer boundaries (Number.MIN_SAFE_INTEGERandNumber.MAX_SAFE_INTEGER), they lose precision (for example,7583362298413593073gets serialized as7583362298413593000). This leads to round-off errors and tool failures when downstream clients/LLMs execute queries using these values.Changes
stringifyEJSONinsrc/helpers/ejson.tsto pre-process objects, converting unsafeLonginstances (or those identified by_bsontype === "Long") into strict EJSON format (i.e.{ $numberLong: "value" }) to preserve exact 64-bit precision. SafeLongs are kept as normal numbers to maintain relaxed mode readability.find,aggregate,aggregate-db, anddb-statstools to usestringifyEJSONinstead of defaultEJSON.stringify.tests/unit/helpers/ejson.test.tsensuring correct precision behavior for safe/unsafe values and deeply nested objects or arrays.Closes #728