Skip to content

Commit bcb30a2

Browse files
committed
JsonBsonEncoder: fix parsing of JsonPrimitive numbers
encodeJsonPrimitive would in some cases attempt to parse scientifically formatted numbers as plain Ints/Longs, which would result in a NumberFormatException.
1 parent 6c4ab1b commit bcb30a2

2 files changed

Lines changed: 41 additions & 14 deletions

File tree

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

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -101,16 +101,16 @@ internal class JsonBsonEncoder(
101101
primitive.isString -> encodeString(content)
102102
content == "true" || content == "false" -> encodeBoolean(content.toBooleanStrict())
103103
else -> {
104-
val decimal = BigDecimal(content)
104+
val decimal = BigDecimal(content).stripTrailingZeros()
105105
when {
106-
decimal.scale() != 0 ->
106+
decimal.scale() > 0 ->
107107
if (DOUBLE_MIN_VALUE <= decimal && decimal <= DOUBLE_MAX_VALUE) {
108108
encodeDouble(primitive.double)
109109
} else {
110110
writer.writeDecimal128(Decimal128(decimal))
111111
}
112-
INT_MIN_VALUE <= decimal && decimal <= INT_MAX_VALUE -> encodeInt(primitive.int)
113-
LONG_MIN_VALUE <= decimal && decimal <= LONG_MAX_VALUE -> encodeLong(primitive.long)
112+
INT_MIN_VALUE <= decimal && decimal <= INT_MAX_VALUE -> encodeInt(decimal.toInt())
113+
LONG_MIN_VALUE <= decimal && decimal <= LONG_MAX_VALUE -> encodeLong(decimal.toLong())
114114
else -> writer.writeDecimal128(Decimal128(decimal))
115115
}
116116
}

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

Lines changed: 37 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ import kotlinx.datetime.LocalTime
2626
import kotlinx.serialization.ExperimentalSerializationApi
2727
import kotlinx.serialization.MissingFieldException
2828
import kotlinx.serialization.SerializationException
29+
import kotlinx.serialization.json.Json
2930
import kotlinx.serialization.json.JsonPrimitive
3031
import kotlinx.serialization.json.buildJsonArray
3132
import kotlinx.serialization.json.buildJsonObject
@@ -116,6 +117,7 @@ import org.bson.codecs.kotlinx.samples.SealedInterface
116117
import org.bson.codecs.kotlinx.samples.ValueClass
117118
import org.bson.json.JsonMode
118119
import org.bson.json.JsonWriterSettings
120+
import org.bson.types.Decimal128
119121
import org.junit.jupiter.api.Test
120122
import org.junit.jupiter.api.assertThrows
121123
import org.junit.jupiter.params.ParameterizedTest
@@ -146,10 +148,10 @@ class KotlinSerializerCodecTest {
146148
| "code": {"${'$'}code": "int i = 0;"},
147149
| "codeWithScope": {"${'$'}code": "int x = y", "${'$'}scope": {"y": 1}},
148150
| "dateTime": {"${'$'}date": {"${'$'}numberLong": "1577836801000"}},
149-
| "decimal128": {"${'$'}numberDecimal": "1.0"},
151+
| "decimal128": {"${'$'}numberDecimal": "1.1"},
150152
| "documentEmpty": {},
151153
| "document": {"a": {"${'$'}numberInt": "1"}},
152-
| "double": {"${'$'}numberDouble": "62.0"},
154+
| "double": {"${'$'}numberDouble": "62.1"},
153155
| "int32": {"${'$'}numberInt": "42"},
154156
| "int64": {"${'$'}numberLong": "52"},
155157
| "maxKey": {"${'$'}maxKey": 1},
@@ -218,6 +220,24 @@ class KotlinSerializerCodecTest {
218220
.append("boolean", BsonBoolean.TRUE)
219221
.append("string", BsonString("String")))
220222
}
223+
224+
@JvmStatic
225+
fun testJsonPrimitiveNumberEncoding(): Stream<Pair<String, String>> {
226+
return Stream.of(
227+
"""{"value": 0}""" to """{"value": 0}""",
228+
"""{"value": 0}""" to """{"value": 0.0}""",
229+
"""{"value": 1.1}""" to """{"value": 1.1E0}""",
230+
"""{"value": 11}""" to """{"value": 1.1E1}""",
231+
"""{"value": 110}""" to """{"value": 1.1E2}""",
232+
"""{"value": 1100}""" to """{"value": 1.1E3}""",
233+
"""{"value": 0.1}""" to """{"value": 1E-1}""",
234+
"""{"value": 0.01}""" to """{"value": 1E-2}""",
235+
"""{"value": 0.001}""" to """{"value": 1E-3}""",
236+
"""{"value": 35485464}""" to """{"value": 35485464}""",
237+
"""{"value": 35485464}""" to """{"value": 35485464.0}""",
238+
"""{"value": {"${'$'}numberDecimal": "123456789123456789123456789"}}""" to """{"value": 123456789123456789123456789}"""
239+
)
240+
}
221241
}
222242

223243
@ParameterizedTest
@@ -832,9 +852,9 @@ class KotlinSerializerCodecTest {
832852
|"short": 1,
833853
|"int": 22,
834854
|"long": {"$numberLong": "3000000000"},
835-
|"decimal": {"$numberDecimal": "10000000000000000000"}
836-
|"decimal2": {"$numberDecimal": "3.1230E+700"}
837-
|"float": 4.0,
855+
|"decimal": {"$numberDecimal": "1E+19"}
856+
|"decimal2": {"$numberDecimal": "3.123E+700"}
857+
|"float": 4.1,
838858
|"double": 4.2,
839859
|"boolean": true,
840860
|"string": "String"
@@ -849,9 +869,9 @@ class KotlinSerializerCodecTest {
849869
put("short", 1)
850870
put("int", 22)
851871
put("long", 3_000_000_000)
852-
put("decimal", BigDecimal("10000000000000000000"))
853-
put("decimal2", BigDecimal("3.1230E+700"))
854-
put("float", 4.0)
872+
put("decimal", BigDecimal("1E+19"))
873+
put("decimal2", BigDecimal("3.123E+700"))
874+
put("float", 4.1)
855875
put("double", 4.2)
856876
put("boolean", true)
857877
put("string", "String")
@@ -1023,10 +1043,10 @@ class KotlinSerializerCodecTest {
10231043
put("binary", JsonPrimitive("S2Fma2Egcm9ja3Mh"))
10241044
put("boolean", JsonPrimitive(true))
10251045
put("dateTime", JsonPrimitive(1577836801000))
1026-
put("decimal128", JsonPrimitive(1.0))
1046+
put("decimal128", JsonPrimitive(1.1))
10271047
put("documentEmpty", buildJsonObject {})
10281048
put("document", buildJsonObject { put("a", JsonPrimitive(1)) })
1029-
put("double", JsonPrimitive(62.0))
1049+
put("double", JsonPrimitive(62.1))
10301050
put("int32", JsonPrimitive(42))
10311051
put("int64", JsonPrimitive(52))
10321052
put("objectId", JsonPrimitive("211111111111111111111112"))
@@ -1050,6 +1070,13 @@ class KotlinSerializerCodecTest {
10501070
assertDecodesTo("""{"value": $jsonAllSupportedTypesDocument}""", dataClassWithAllSupportedJsonTypes)
10511071
}
10521072

1073+
@ParameterizedTest
1074+
@MethodSource("testJsonPrimitiveNumberEncoding")
1075+
fun testJsonPrimitiveNumberEncoding(test: Pair<String, String>) {
1076+
val (expected, actual) = test
1077+
assertEncodesTo(expected, Json.parseToJsonElement(actual))
1078+
}
1079+
10531080
@Test
10541081
fun testDataFailures() {
10551082
assertThrows<MissingFieldException>("Missing data") {

0 commit comments

Comments
 (0)