JsonBsonEncoder: fix parsing of JsonPrimitive numbers#1937
JsonBsonEncoder: fix parsing of JsonPrimitive numbers#1937berlix wants to merge 2 commits intomongodb:mainfrom
Conversation
encodeJsonPrimitive would in some cases attempt to parse scientifically formatted numbers as plain Ints/Longs, which would result in a NumberFormatException.
There was a problem hiding this comment.
Pull request overview
Fixes numeric handling in JsonBsonEncoder.encodeJsonPrimitive so scientifically formatted JSON numbers aren’t incorrectly parsed as Int/Long (avoiding NumberFormatException), and updates/extends tests to cover the corrected behavior and the new “minimal decimals” encoding.
Changes:
- Normalize numeric
JsonPrimitivevalues viaBigDecimal(...).stripTrailingZeros()and useBigDecimal.toInt()/toLong()for integer-range encoding. - Update existing JSON element expectations to match the new normalized numeric output (e.g.,
1E+19,3.123E+700, non-.0doubles). - Add a parameterized test that exercises scientific notation and large-number cases.
Reviewed changes
Copilot reviewed 2 out of 2 changed files in this pull request and generated 3 comments.
| File | Description |
|---|---|
bson-kotlinx/src/main/kotlin/org/bson/codecs/kotlinx/JsonBsonEncoder.kt |
Adjusts numeric parsing/branching for JsonPrimitive numbers to properly handle scientific notation and normalized integer detection. |
bson-kotlinx/src/test/kotlin/org/bson/codecs/kotlinx/KotlinSerializerCodecTest.kt |
Adds coverage for scientific-notation number encoding and updates expected JSON outputs to match normalized formatting. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| decimal.scale() > 0 -> | ||
| if (DOUBLE_MIN_VALUE <= decimal && decimal <= DOUBLE_MAX_VALUE) { | ||
| encodeDouble(primitive.double) | ||
| } else { | ||
| writer.writeDecimal128(Decimal128(decimal)) | ||
| } |
There was a problem hiding this comment.
The double-range check for fractional numbers only allows values >= Double.MIN_VALUE, which is positive. This causes negative fractional values (e.g., -1.1) to skip the double branch and be encoded as Decimal128 instead of a BSON double. Consider checking the absolute value (and treating 0 as a valid double) while still enforcing the finite double range, e.g., using decimal.abs() against Double.MIN_VALUE/Double.MAX_VALUE (and decimal == 0 special-case).
| decimal.scale() > 0 -> | |
| if (DOUBLE_MIN_VALUE <= decimal && decimal <= DOUBLE_MAX_VALUE) { | |
| encodeDouble(primitive.double) | |
| } else { | |
| writer.writeDecimal128(Decimal128(decimal)) | |
| } | |
| decimal.scale() > 0 -> { | |
| val absoluteDecimal = decimal.abs() | |
| if (decimal.signum() == 0 || (DOUBLE_MIN_VALUE <= absoluteDecimal && absoluteDecimal <= DOUBLE_MAX_VALUE)) { | |
| encodeDouble(primitive.double) | |
| } else { | |
| writer.writeDecimal128(Decimal128(decimal)) | |
| } | |
| } |
There was a problem hiding this comment.
Addressed in 817845e, with appropriate test cases. I removed the check for DOUBLE_MIN_VALUE entirely, since it wouldn't make a ton of sense - if anything, we'd want to switch to Decimal128's precision whenever Double cannot represent the original number within some (arbitrary) precision (say, max. 1% deviation), and not only when the number happens to be close to zero. But that seems like an inappropriate amount of guesswork to add here.
For the record, this issue was not introduced by my PR. :-)
| import org.bson.codecs.kotlinx.samples.ValueClass | ||
| import org.bson.json.JsonMode | ||
| import org.bson.json.JsonWriterSettings | ||
| import org.bson.types.Decimal128 |
There was a problem hiding this comment.
Decimal128 is imported but not referenced in this test file (the code uses asDecimal128() which returns BsonDecimal128). This will be flagged as an unused import by static analysis; please remove it.
| import org.bson.types.Decimal128 |
| """{"value": 0}""" to """{"value": 0}""", | ||
| """{"value": 0}""" to """{"value": 0.0}""", | ||
| """{"value": 1.1}""" to """{"value": 1.1E0}""", | ||
| """{"value": 11}""" to """{"value": 1.1E1}""", | ||
| """{"value": 110}""" to """{"value": 1.1E2}""", | ||
| """{"value": 1100}""" to """{"value": 1.1E3}""", | ||
| """{"value": 0.1}""" to """{"value": 1E-1}""", | ||
| """{"value": 0.01}""" to """{"value": 1E-2}""", | ||
| """{"value": 0.001}""" to """{"value": 1E-3}""", | ||
| """{"value": 35485464}""" to """{"value": 35485464}""", | ||
| """{"value": 35485464}""" to """{"value": 35485464.0}""", | ||
| """{"value": {"${'$'}numberDecimal": "123456789123456789123456789"}}""" to """{"value": 123456789123456789123456789}""" |
There was a problem hiding this comment.
The newly added testJsonPrimitiveNumberEncoding() MethodSource data is not formatted consistently (indentation is uneven across elements). Since the repo uses Spotless/ktfmt, this is likely to fail spotlessCheck; please reformat this block (e.g., run ./gradlew spotlessApply) so the Stream.of(...) entries are properly aligned.
| """{"value": 0}""" to """{"value": 0}""", | |
| """{"value": 0}""" to """{"value": 0.0}""", | |
| """{"value": 1.1}""" to """{"value": 1.1E0}""", | |
| """{"value": 11}""" to """{"value": 1.1E1}""", | |
| """{"value": 110}""" to """{"value": 1.1E2}""", | |
| """{"value": 1100}""" to """{"value": 1.1E3}""", | |
| """{"value": 0.1}""" to """{"value": 1E-1}""", | |
| """{"value": 0.01}""" to """{"value": 1E-2}""", | |
| """{"value": 0.001}""" to """{"value": 1E-3}""", | |
| """{"value": 35485464}""" to """{"value": 35485464}""", | |
| """{"value": 35485464}""" to """{"value": 35485464.0}""", | |
| """{"value": {"${'$'}numberDecimal": "123456789123456789123456789"}}""" to """{"value": 123456789123456789123456789}""" | |
| """{"value": 0}""" to """{"value": 0}""", | |
| """{"value": 0}""" to """{"value": 0.0}""", | |
| """{"value": 1.1}""" to """{"value": 1.1E0}""", | |
| """{"value": 11}""" to """{"value": 1.1E1}""", | |
| """{"value": 110}""" to """{"value": 1.1E2}""", | |
| """{"value": 1100}""" to """{"value": 1.1E3}""", | |
| """{"value": 0.1}""" to """{"value": 1E-1}""", | |
| """{"value": 0.01}""" to """{"value": 1E-2}""", | |
| """{"value": 0.001}""" to """{"value": 1E-3}""", | |
| """{"value": 35485464}""" to """{"value": 35485464}""", | |
| """{"value": 35485464}""" to """{"value": 35485464.0}""", | |
| """{"value": {"${'$'}numberDecimal": "123456789123456789123456789"}}""" to """{"value": 123456789123456789123456789}""" |
Also fixes formatting in KotlinSerializerCodecTest.kt.
encodeJsonPrimitivewould in some cases attempt to parse scientifically formatted numbers as plain Ints/Longs, which would result in aNumberFormatException.The new test case
testJsonPrimitiveNumberEncodingreproduces the issue/verifies the fix.Note that the adjusted implementation always encodes numbers with the least amount of decimals necessary, which is why some other test cases needed to be adjusted.
JAVA-6165