@@ -26,6 +26,7 @@ import kotlinx.datetime.LocalTime
2626import kotlinx.serialization.ExperimentalSerializationApi
2727import kotlinx.serialization.MissingFieldException
2828import kotlinx.serialization.SerializationException
29+ import kotlinx.serialization.json.Json
2930import kotlinx.serialization.json.JsonPrimitive
3031import kotlinx.serialization.json.buildJsonArray
3132import kotlinx.serialization.json.buildJsonObject
@@ -87,6 +88,7 @@ import org.bson.codecs.kotlinx.samples.DataClassWithContextualDateValues
8788import org.bson.codecs.kotlinx.samples.DataClassWithDataClassMapKey
8889import org.bson.codecs.kotlinx.samples.DataClassWithDateValues
8990import org.bson.codecs.kotlinx.samples.DataClassWithDefaults
91+ import org.bson.codecs.kotlinx.samples.DataClassWithDefaultsAndNulls
9092import org.bson.codecs.kotlinx.samples.DataClassWithEmbedded
9193import org.bson.codecs.kotlinx.samples.DataClassWithEncodeDefault
9294import org.bson.codecs.kotlinx.samples.DataClassWithEnum
@@ -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,35 @@ 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": -1.1}""" to """ {"value": -1.1E0}""" ,
237+ """ {"value": -11}""" to """ {"value": -1.1E1}""" ,
238+ """ {"value": -110}""" to """ {"value": -1.1E2}""" ,
239+ """ {"value": -1100}""" to """ {"value": -1.1E3}""" ,
240+ """ {"value": -0.1}""" to """ {"value": -1E-1}""" ,
241+ """ {"value": -0.01}""" to """ {"value": -1E-2}""" ,
242+ """ {"value": -0.001}""" to """ {"value": -1E-3}""" ,
243+ """ {"value": 9223372036854775807}""" to """ {"value": 9223372036854775807}""" ,
244+ """ {"value": {"${' $' } numberDecimal": "9223372036854775808"}}""" to """ {"value": 9223372036854775808}""" ,
245+ """ {"value": -9223372036854775808}""" to """ {"value": -9223372036854775808}""" ,
246+ """ {"value": {"${' $' } numberDecimal": "-9223372036854775809"}}""" to
247+ """ {"value": -9223372036854775809}""" ,
248+ """ {"value": {"${' $' } numberDecimal": "1.8E+309"}}""" to """ {"value": 1.8E+309}""" ,
249+ """ {"value": {"${' $' } numberDecimal": "1E-325"}}""" to """ {"value": 1E-325}""" ,
250+ )
251+ }
221252 }
222253
223254 @ParameterizedTest
@@ -303,28 +334,71 @@ class KotlinSerializerCodecTest {
303334 |}"""
304335 .trimMargin()
305336
306- val defaultDataClass = DataClassWithDefaults ()
307- assertRoundTrips(expectedDefault, defaultDataClass)
308- assertRoundTrips(emptyDocument, defaultDataClass, altConfiguration)
337+ assertRoundTrips(expectedDefault, DataClassWithDefaults ())
309338
310- val expectedSomeOverrides = """ {"boolean": true, "listSimple": ["a"]}"""
311- val someOverridesDataClass = DataClassWithDefaults (boolean = true , listSimple = listOf (" a" ))
312- assertRoundTrips(expectedSomeOverrides, someOverridesDataClass, altConfiguration)
339+ // Assert no data decodes as expected
340+ assertDecodesTo(BsonDocument .parse(emptyDocument), DataClassWithDefaults ())
341+
342+ // Assert some data
343+ assertDecodesTo(BsonDocument .parse(""" {"string": "Custom"}""" ), DataClassWithDefaults (string = " Custom" ))
344+
345+ // Assert all data
346+ val expected =
347+ """ {
348+ | "boolean": true,
349+ | "string": "Custom",
350+ | "listSimple": ["x"]
351+ |}"""
352+ .trimMargin()
353+
354+ assertRoundTrips(expected, DataClassWithDefaults (boolean = true , string = " Custom" , listSimple = listOf (" x" )))
313355 }
314356
315357 @Test
316358 fun testDataClassWithNulls () {
317- val expectedNulls =
359+ val dataClass = DataClassWithNulls (null , null , null )
360+ assertRoundTrips(emptyDocument, dataClass)
361+
362+ // Assert all null data decodes as expected
363+ assertDecodesTo(BsonDocument .parse(""" {"boolean": null, "string": null, "listSimple": null}""" ), dataClass)
364+
365+ // Assert some data
366+ assertDecodesTo(BsonDocument .parse(""" {"string": "Custom"}""" ), DataClassWithNulls (null , " Custom" , null ))
367+
368+ // Assert all data
369+ val expected =
318370 """ {
319- | "boolean": null ,
320- | "string": null ,
321- | "listSimple": null
371+ | "boolean": true ,
372+ | "string": "Custom" ,
373+ | "listSimple": ["x"]
322374 |}"""
323375 .trimMargin()
376+ assertRoundTrips(expected, DataClassWithNulls (true , " Custom" , listOf (" x" )))
377+ }
324378
325- val dataClass = DataClassWithNulls (null , null , null )
326- assertRoundTrips(emptyDocument, dataClass)
327- assertRoundTrips(expectedNulls, dataClass, altConfiguration)
379+ @Test
380+ fun testDataClassWithDefaultsAndNulls () {
381+ // All fields provided
382+ val expected = """ {"required": "req", "optional": "opt", "nullable": "nul"}"""
383+ assertRoundTrips(expected, DataClassWithDefaultsAndNulls (" req" , " opt" , " nul" ))
384+
385+ // Only required field — optional gets default, nullable gets default (null)
386+ assertDecodesTo(BsonDocument .parse(""" {"required": "req"}""" ), DataClassWithDefaultsAndNulls (" req" ))
387+
388+ // Required + nullable explicit null in document
389+ assertDecodesTo(
390+ BsonDocument .parse(""" {"required": "req", "nullable": null}""" ), DataClassWithDefaultsAndNulls (" req" ))
391+
392+ // Required + optional overridden, nullable absent
393+ assertDecodesTo(
394+ BsonDocument .parse(""" {"required": "req", "optional": "custom"}""" ),
395+ DataClassWithDefaultsAndNulls (" req" , " custom" ))
396+
397+ // Missing required field throws
398+ assertThrows<MissingFieldException > {
399+ val codec = KotlinSerializerCodec .create(DataClassWithDefaultsAndNulls ::class )
400+ codec?.decode(BsonDocumentReader (BsonDocument ()), DecoderContext .builder().build())
401+ }
328402 }
329403
330404 @Test
@@ -832,9 +906,9 @@ class KotlinSerializerCodecTest {
832906 |"short": 1,
833907 |"int": 22,
834908 |"long": {"$numberLong ": "3000000000"},
835- |"decimal": {"$numberDecimal ": "10000000000000000000 "}
836- |"decimal2": {"$numberDecimal ": "3.1230E +700"}
837- |"float": 4.0 ,
909+ |"decimal": {"$numberDecimal ": "1E+19 "}
910+ |"decimal2": {"$numberDecimal ": "3.123E +700"}
911+ |"float": 4.1 ,
838912 |"double": 4.2,
839913 |"boolean": true,
840914 |"string": "String"
@@ -849,9 +923,9 @@ class KotlinSerializerCodecTest {
849923 put(" short" , 1 )
850924 put(" int" , 22 )
851925 put(" long" , 3_000_000_000 )
852- put(" decimal" , BigDecimal (" 10000000000000000000 " ))
853- put(" decimal2" , BigDecimal (" 3.1230E +700" ))
854- put(" float" , 4.0 )
926+ put(" decimal" , BigDecimal (" 1E+19 " ))
927+ put(" decimal2" , BigDecimal (" 3.123E +700" ))
928+ put(" float" , 4.1 )
855929 put(" double" , 4.2 )
856930 put(" boolean" , true )
857931 put(" string" , " String" )
@@ -1023,10 +1097,10 @@ class KotlinSerializerCodecTest {
10231097 put(" binary" , JsonPrimitive (" S2Fma2Egcm9ja3Mh" ))
10241098 put(" boolean" , JsonPrimitive (true ))
10251099 put(" dateTime" , JsonPrimitive (1577836801000 ))
1026- put(" decimal128" , JsonPrimitive (1.0 ))
1100+ put(" decimal128" , JsonPrimitive (1.1 ))
10271101 put(" documentEmpty" , buildJsonObject {})
10281102 put(" document" , buildJsonObject { put(" a" , JsonPrimitive (1 )) })
1029- put(" double" , JsonPrimitive (62.0 ))
1103+ put(" double" , JsonPrimitive (62.1 ))
10301104 put(" int32" , JsonPrimitive (42 ))
10311105 put(" int64" , JsonPrimitive (52 ))
10321106 put(" objectId" , JsonPrimitive (" 211111111111111111111112" ))
@@ -1050,6 +1124,13 @@ class KotlinSerializerCodecTest {
10501124 assertDecodesTo(""" {"value": $jsonAllSupportedTypesDocument }""" , dataClassWithAllSupportedJsonTypes)
10511125 }
10521126
1127+ @ParameterizedTest
1128+ @MethodSource(" testJsonPrimitiveNumberEncoding" )
1129+ fun testJsonPrimitiveNumberEncoding (test : Pair <String , String >) {
1130+ val (expected, actual) = test
1131+ assertEncodesTo(expected, Json .parseToJsonElement(actual))
1132+ }
1133+
10531134 @Test
10541135 fun testDataFailures () {
10551136 assertThrows<MissingFieldException >(" Missing data" ) {
0 commit comments