Skip to content

Commit a75bf27

Browse files
berlixrozza
andauthored
JsonBsonEncoder: fix parsing of JsonPrimitive numbers (#1937)
encodeJsonPrimitive would in some cases attempt to parse scientifically formatted numbers as plain Ints/Longs, which would result in a NumberFormatException. --- Co-authored-by: Ross Lawley <[email protected]>
1 parent ccca236 commit a75bf27

2 files changed

Lines changed: 56 additions & 20 deletions

File tree

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

Lines changed: 9 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -25,9 +25,6 @@ import kotlinx.serialization.json.JsonEncoder
2525
import kotlinx.serialization.json.JsonNull
2626
import kotlinx.serialization.json.JsonObject
2727
import kotlinx.serialization.json.JsonPrimitive
28-
import kotlinx.serialization.json.double
29-
import kotlinx.serialization.json.int
30-
import kotlinx.serialization.json.long
3128
import kotlinx.serialization.modules.SerializersModule
3229
import org.bson.BsonWriter
3330
import org.bson.codecs.kotlinx.utils.BsonCodecUtils.toJsonNamingStrategy
@@ -41,8 +38,8 @@ internal class JsonBsonEncoder(
4138
) : BsonEncoderImpl(writer, serializersModule, configuration), JsonEncoder {
4239

4340
companion object {
44-
private val DOUBLE_MIN_VALUE = BigDecimal.valueOf(Double.MIN_VALUE)
4541
private val DOUBLE_MAX_VALUE = BigDecimal.valueOf(Double.MAX_VALUE)
42+
private val DOUBLE_MIN_VALUE = BigDecimal.valueOf(Double.MIN_VALUE)
4643
private val INT_MIN_VALUE = BigDecimal.valueOf(Int.MIN_VALUE.toLong())
4744
private val INT_MAX_VALUE = BigDecimal.valueOf(Int.MAX_VALUE.toLong())
4845
private val LONG_MIN_VALUE = BigDecimal.valueOf(Long.MIN_VALUE)
@@ -101,16 +98,18 @@ internal class JsonBsonEncoder(
10198
primitive.isString -> encodeString(content)
10299
content == "true" || content == "false" -> encodeBoolean(content.toBooleanStrict())
103100
else -> {
104-
val decimal = BigDecimal(content)
101+
val decimal = BigDecimal(content).stripTrailingZeros()
105102
when {
106-
decimal.scale() != 0 ->
107-
if (DOUBLE_MIN_VALUE <= decimal && decimal <= DOUBLE_MAX_VALUE) {
108-
encodeDouble(primitive.double)
103+
decimal.scale() > 0 -> {
104+
val abs = decimal.abs()
105+
if ((decimal.signum() == 0 || abs >= DOUBLE_MIN_VALUE) && abs <= DOUBLE_MAX_VALUE) {
106+
encodeDouble(decimal.toDouble())
109107
} else {
110108
writer.writeDecimal128(Decimal128(decimal))
111109
}
112-
INT_MIN_VALUE <= decimal && decimal <= INT_MAX_VALUE -> encodeInt(primitive.int)
113-
LONG_MIN_VALUE <= decimal && decimal <= LONG_MAX_VALUE -> encodeLong(primitive.long)
110+
}
111+
INT_MIN_VALUE <= decimal && decimal <= INT_MAX_VALUE -> encodeInt(decimal.toInt())
112+
LONG_MIN_VALUE <= decimal && decimal <= LONG_MAX_VALUE -> encodeLong(decimal.toLong())
114113
else -> writer.writeDecimal128(Decimal128(decimal))
115114
}
116115
}

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

Lines changed: 47 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
@@ -146,10 +147,10 @@ class KotlinSerializerCodecTest {
146147
| "code": {"${'$'}code": "int i = 0;"},
147148
| "codeWithScope": {"${'$'}code": "int x = y", "${'$'}scope": {"y": 1}},
148149
| "dateTime": {"${'$'}date": {"${'$'}numberLong": "1577836801000"}},
149-
| "decimal128": {"${'$'}numberDecimal": "1.0"},
150+
| "decimal128": {"${'$'}numberDecimal": "1.1"},
150151
| "documentEmpty": {},
151152
| "document": {"a": {"${'$'}numberInt": "1"}},
152-
| "double": {"${'$'}numberDouble": "62.0"},
153+
| "double": {"${'$'}numberDouble": "62.1"},
153154
| "int32": {"${'$'}numberInt": "42"},
154155
| "int64": {"${'$'}numberLong": "52"},
155156
| "maxKey": {"${'$'}maxKey": 1},
@@ -218,6 +219,35 @@ class KotlinSerializerCodecTest {
218219
.append("boolean", BsonBoolean.TRUE)
219220
.append("string", BsonString("String")))
220221
}
222+
223+
@JvmStatic
224+
fun testJsonPrimitiveNumberEncoding(): Stream<Pair<String, String>> {
225+
return Stream.of(
226+
"""{"value": 0}""" to """{"value": 0}""",
227+
"""{"value": 0}""" to """{"value": 0.0}""",
228+
"""{"value": 1.1}""" to """{"value": 1.1E0}""",
229+
"""{"value": 11}""" to """{"value": 1.1E1}""",
230+
"""{"value": 110}""" to """{"value": 1.1E2}""",
231+
"""{"value": 1100}""" to """{"value": 1.1E3}""",
232+
"""{"value": 0.1}""" to """{"value": 1E-1}""",
233+
"""{"value": 0.01}""" to """{"value": 1E-2}""",
234+
"""{"value": 0.001}""" to """{"value": 1E-3}""",
235+
"""{"value": -1.1}""" to """{"value": -1.1E0}""",
236+
"""{"value": -11}""" to """{"value": -1.1E1}""",
237+
"""{"value": -110}""" to """{"value": -1.1E2}""",
238+
"""{"value": -1100}""" to """{"value": -1.1E3}""",
239+
"""{"value": -0.1}""" to """{"value": -1E-1}""",
240+
"""{"value": -0.01}""" to """{"value": -1E-2}""",
241+
"""{"value": -0.001}""" to """{"value": -1E-3}""",
242+
"""{"value": 9223372036854775807}""" to """{"value": 9223372036854775807}""",
243+
"""{"value": {"${'$'}numberDecimal": "9223372036854775808"}}""" to """{"value": 9223372036854775808}""",
244+
"""{"value": -9223372036854775808}""" to """{"value": -9223372036854775808}""",
245+
"""{"value": {"${'$'}numberDecimal": "-9223372036854775809"}}""" to
246+
"""{"value": -9223372036854775809}""",
247+
"""{"value": {"${'$'}numberDecimal": "1.8E+309"}}""" to """{"value": 1.8E+309}""",
248+
"""{"value": {"${'$'}numberDecimal": "1E-325"}}""" to """{"value": 1E-325}""",
249+
)
250+
}
221251
}
222252

223253
@ParameterizedTest
@@ -832,9 +862,9 @@ class KotlinSerializerCodecTest {
832862
|"short": 1,
833863
|"int": 22,
834864
|"long": {"$numberLong": "3000000000"},
835-
|"decimal": {"$numberDecimal": "10000000000000000000"}
836-
|"decimal2": {"$numberDecimal": "3.1230E+700"}
837-
|"float": 4.0,
865+
|"decimal": {"$numberDecimal": "1E+19"}
866+
|"decimal2": {"$numberDecimal": "3.123E+700"}
867+
|"float": 4.1,
838868
|"double": 4.2,
839869
|"boolean": true,
840870
|"string": "String"
@@ -849,9 +879,9 @@ class KotlinSerializerCodecTest {
849879
put("short", 1)
850880
put("int", 22)
851881
put("long", 3_000_000_000)
852-
put("decimal", BigDecimal("10000000000000000000"))
853-
put("decimal2", BigDecimal("3.1230E+700"))
854-
put("float", 4.0)
882+
put("decimal", BigDecimal("1E+19"))
883+
put("decimal2", BigDecimal("3.123E+700"))
884+
put("float", 4.1)
855885
put("double", 4.2)
856886
put("boolean", true)
857887
put("string", "String")
@@ -1023,10 +1053,10 @@ class KotlinSerializerCodecTest {
10231053
put("binary", JsonPrimitive("S2Fma2Egcm9ja3Mh"))
10241054
put("boolean", JsonPrimitive(true))
10251055
put("dateTime", JsonPrimitive(1577836801000))
1026-
put("decimal128", JsonPrimitive(1.0))
1056+
put("decimal128", JsonPrimitive(1.1))
10271057
put("documentEmpty", buildJsonObject {})
10281058
put("document", buildJsonObject { put("a", JsonPrimitive(1)) })
1029-
put("double", JsonPrimitive(62.0))
1059+
put("double", JsonPrimitive(62.1))
10301060
put("int32", JsonPrimitive(42))
10311061
put("int64", JsonPrimitive(52))
10321062
put("objectId", JsonPrimitive("211111111111111111111112"))
@@ -1050,6 +1080,13 @@ class KotlinSerializerCodecTest {
10501080
assertDecodesTo("""{"value": $jsonAllSupportedTypesDocument}""", dataClassWithAllSupportedJsonTypes)
10511081
}
10521082

1083+
@ParameterizedTest
1084+
@MethodSource("testJsonPrimitiveNumberEncoding")
1085+
fun testJsonPrimitiveNumberEncoding(test: Pair<String, String>) {
1086+
val (expected, actual) = test
1087+
assertEncodesTo(expected, Json.parseToJsonElement(actual))
1088+
}
1089+
10531090
@Test
10541091
fun testDataFailures() {
10551092
assertThrows<MissingFieldException>("Missing data") {

0 commit comments

Comments
 (0)