Fix regression in JSON primitive handling - #2034
Conversation
|
Here is an overview of the changes:
|
There was a problem hiding this comment.
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
JsonBsonEncodernumeric 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
JsonElementproperties (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.
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>
There was a problem hiding this comment.
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", " "])
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