Skip to content

Fix regression in JSON primitive handling - #2034

Draft
rozza wants to merge 1 commit into
mongodb:mainfrom
rozza:JAVA-6280
Draft

Fix regression in JSON primitive handling#2034
rozza wants to merge 1 commit into
mongodb:mainfrom
rozza:JAVA-6280

Conversation

@rozza

@rozza rozza commented Aug 11, 2026

Copy link
Copy Markdown
Member

JSON floating-point literals with an integral value were encoded as integer BSON types.

Change to type the literal from its text instead. A literal containing a fraction or an exponent is encoded as a BSON double, matching how the driver's own JSON parser types numbers.

Outside the range of the matching BSON type a literal still widens to a Decimal128 rather than losing data.

Because kotlinx.serialization has no BigDecimal support, a BigDecimal in a JsonObject is stored as its toString() and cannot be distinguished from a hand-written literal, so BigDecimal("1E+19") encodes as a double. Callers needing an exact BSON type should declare a typed property such as Decimal128 or BsonValue.

JAVA-6280

@rozza

rozza commented Aug 11, 2026

Copy link
Copy Markdown
Member Author

Here is an overview of the changes:

literal 5.6.0 5.7 - 5.9 Now
3 INT32 INT32 INT32
3.0 DOUBLE INT32 DOUBLE
-3.0 DECIMAL128 INT32 DOUBLE
0.0 DECIMAL128 INT32 DOUBLE
-0.0 DECIMAL128 INT32 DOUBLE (sign kept)
1.1 DOUBLE DOUBLE DOUBLE
-1.1 DECIMAL128 DOUBLE DOUBLE
11.0 DOUBLE INT32 DOUBLE
1.1E1 crash INT32 DOUBLE
3.0E1 crash INT32 DOUBLE
1e20 DOUBLE DECIMAL128 DOUBLE
-1e20 DECIMAL128 DECIMAL128 DOUBLE
1E5 DOUBLE INT32 DOUBLE
1E+19 DOUBLE DECIMAL128 DOUBLE
1E-1 DOUBLE DOUBLE DOUBLE
1.7976931348623157E308 (Double.MAX_VALUE) DOUBLE DECIMAL128 DOUBLE
1e-320 (subnormal) DOUBLE DOUBLE DOUBLE
1e400 DECIMAL128 DECIMAL128 DECIMAL128
1e-330 DECIMAL128 DECIMAL128 DECIMAL128
9223372036854775808 DECIMAL128 DECIMAL128 DECIMAL128

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

Fixes a regression in bson-kotlinx JSON primitive number encoding by typing numeric literals based on their original text (fraction/exponent → Double, otherwise narrowest integral type, widening to Decimal128 when needed), aligning behavior with the driver’s org.bson.json.JsonScanner while avoiding precision loss where possible.

Changes:

  • Update JsonBsonEncoder numeric literal handling to infer floating vs integral from literal text and improve error reporting for invalid/unrepresentable literals.
  • Expand test coverage for numeric literal typing, Decimal128 widening, non-finite doubles, and invalid unquoted literals.
  • Add user-facing documentation clarifying limitations of JsonElement properties (plain JSON, no Extended JSON typing preservation).

Reviewed changes

Copilot reviewed 3 out of 3 changed files in this pull request and generated 1 comment.

File Description
bson-kotlinx/src/main/kotlin/org/bson/codecs/kotlinx/JsonBsonEncoder.kt Reworks JSON primitive number typing logic; adds guarded parsing + clearer exceptions.
bson-kotlinx/src/main/kotlin/org/bson/codecs/kotlinx/KotlinSerializerCodec.kt Adds KDoc explaining JsonElement property limitations and recommended typed alternatives.
bson-kotlinx/src/test/kotlin/org/bson/codecs/kotlinx/KotlinSerializerCodecTest.kt Adds/updates tests validating numeric typing behavior and error cases.

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment thread bson-kotlinx/src/main/kotlin/org/bson/codecs/kotlinx/JsonBsonEncoder.kt Outdated
JSON floating-point literals with an integral value were encoded as
integer BSON types.

Change to type the literal from its text instead. A literal
containing a fraction or an exponent is encoded as a BSON double,
matching how the driver's own JSON parser types numbers.

Outside the range of the matching BSON type a literal still widens to a
Decimal128 rather than losing data.

Because kotlinx.serialization has no BigDecimal support, a BigDecimal in
a JsonObject is stored as its toString() and cannot be distinguished
from a hand-written literal, so BigDecimal("1E+19") encodes as a double.
Callers needing an exact BSON type should declare a typed property such
as Decimal128 or BsonValue.

Invalid and unrepresentable numeric literals now report a
SerializationException rather than a raw NumberFormatException. BigDecimal
accepts any Unicode decimal digit whereas JSON numbers are ASCII, so the
characters are checked before parsing, keeping integral and floating
literals consistent.

JAVA-6280

Co-authored-by: Pritam Acharya <pritamacharya.work@gmail.com>

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 3 out of 3 changed files in this pull request and generated no new comments.

Suppressed comments (2)

bson-kotlinx/src/main/kotlin/org/bson/codecs/kotlinx/JsonBsonEncoder.kt:181

  • parseNumericLiteral allows '+' anywhere via the character whitelist ("+-.eE"), but JSON numbers only allow '+' as the optional exponent sign (e.g., "1e+20") and never as a leading sign ("+1" is invalid JSON). This can allow JsonUnquotedLiteral("+1") to be encoded successfully even though the exception message claims the literal is "not a valid JSON number".
    private fun parseNumericLiteral(content: String): BigDecimal {
        if (content.any { it !in '0'..'9' && it !in "+-.eE" }) throw notANumber(content)
        return try {

bson-kotlinx/src/test/kotlin/org/bson/codecs/kotlinx/KotlinSerializerCodecTest.kt:1336

  • The invalid-literal test set does not currently include a leading '+' case. Since JSON does not permit a leading '+', add a "+1" sample to lock in the intended validation behavior for JsonUnquotedLiteral inputs.
    // BigDecimal accepts any Unicode decimal digit, but JSON numbers are ASCII.
    // Reject non-ASCII digits on the integral path too, not only the floating one.
    @ValueSource(strings = ["١٢٣", "123", "١.٥", "1.5", "١e٢", "abc", "", " ", "1_000", "0x10", "1d", " "])

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.

2 participants