/* * Copyright 2017-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license. */ package kotlinx.serialization.json import kotlinx.serialization.builtins.* import kotlinx.serialization.descriptors.* import kotlinx.serialization.encoding.* import kotlinx.serialization.json.internal.* import kotlinx.serialization.* import kotlinx.serialization.modules.* import kotlin.test.* @Suppress("UnsafeCastFromDynamic") class EncodeToDynamicTest { @Serializable data class Data(val a: Int) @Serializable open class DataWrapper(open val s: String, val d: Data? = Data(1)) { override fun equals(other: Any?): Boolean { if (this === other) return true if (other == null || this::class.js != other::class.js) return false other as DataWrapper if (s != other.s) return false if (d != other.d) return false return true } override fun hashCode(): Int { var result = s.hashCode() result = 31 * result + (d?.hashCode() ?: 0) return result } } @Serializable data class NestedList(val a: String, val list: List) @Serializable data class ListOfLists(val l: List>) @Serializable data class MapWrapper(val m: Map) @Serializable data class ComplexMapWrapper(val m: Map) @Serializable data class WithChar(val a: Char) @Serializable data class WithLong(val l: Long) @Serializable data class AllTypes( val b: Byte, val s: Short, val i: Int, val f: Float, val d: Double, val c: Char, val B: Boolean, val S: String ) @Serializable data class EnumWrapper(val e: Color) @Serializable sealed class Sealed { @Serializable data class One(val string: String) : Sealed() } @Serializable class WithJsName(@JsName("b") val a: String) @Serializable data class WithSerialName(@SerialName("b") val a: String) @Serializable enum class Color { RED, GREEN, @SerialName("red") WITH_SERIALNAME_red } @Serializable(MyFancyClass.Companion::class) data class MyFancyClass(val value: String) { companion object : KSerializer { override val descriptor: SerialDescriptor = PrimitiveSerialDescriptor("MyFancyClass", PrimitiveKind.STRING) override fun serialize(encoder: Encoder, value: MyFancyClass) { encoder.encodeString("fancy ${value.value}") } override fun deserialize(decoder: Decoder): MyFancyClass { return MyFancyClass(decoder.decodeString().removePrefix("fancy ")) } } } @Test fun dynamicSimpleTest() { assertDynamicForm(Data(42)) { data, serialized -> assertEquals(data.a, serialized.a) } assertDynamicForm(WithChar('c')) { data, serialized -> assertEquals(data.a.toString(), serialized.a) } assertDynamicForm(AllTypes(1, 2, 3, 4.0f, 5.0, 'c', true, "string")) assertDynamicForm(WithLong(5L)) assertDynamicForm(WithLong(MAX_SAFE_INTEGER.toLong())) assertDynamicForm(WithLong(MAX_SAFE_INTEGER.unaryMinus().toLong())) } @Test fun wrappedObjectsTest() { assertDynamicForm(DataWrapper("a string", Data(42))) { data, serialized -> assertEquals(data.s, serialized.s) assertNotNull(serialized.d) assertEquals(data.d?.a, serialized.d.a) } } @Test fun listTest() { assertDynamicForm(listOf(1, 2, 3, 44), serializer = ListSerializer(Int.serializer())) { data, serialized -> assertNotNull(serialized.length, "length property should exist") assertEquals(data.size, serialized.length) for (i in data.indices) { assertEquals(data[i], serialized[i]) } } } @Test fun arrayTest() { assertDynamicForm(intArrayOf(1, 2, 3, 44), serializer = IntArraySerializer(), true) { data, serialized -> assertNotNull(serialized.length, "length property should exist") assertEquals(data.size, serialized.length) for (i in data.indices) { assertEquals(data[i], serialized[i]) } } } @Test fun nestedListTest() { assertDynamicForm(NestedList("a string", listOf(1, 2, 3, 44))) { data, serialized -> assertEquals(data.a, serialized.a) assertNotNull(serialized.list.length, "length property should exist") assertEquals(data.list.size, serialized.list.length) for (i in data.list.indices) { assertEquals(data.list[i], serialized.list[i]) } } } @Test fun complexMapWrapperTest() { assertDynamicForm(ComplexMapWrapper(mapOf("key1" to Data(1), "key2" to Data(2)))) } @Test fun mapWrapperTest() { assertDynamicForm(MapWrapper(mapOf("key1" to 1, "key2" to 2))) } @Test fun listOfListsTest() { assertDynamicForm( ListOfLists( listOf( listOf(Data(11), Data(12), Data(13)), listOf(Data(21), Data(22)) ) ) ) { data, serialized -> assertEquals(data.l.size, serialized.l.length) assertEquals(data.l.first().size, serialized.l[0].length) } } @Serializable data class NestedCollections(val data: Map>>) @Test fun nestedCollections() { assertDynamicForm( NestedCollections( mapOf( "one" to mapOf("oneone" to listOf(11, 12, 13), "onetwo" to listOf(1)), "two" to mapOf("twotwo" to listOf(22, 23)) ) ) , serializer = NestedCollections.serializer() ) } @Test fun enums() { assertDynamicForm(EnumWrapper(Color.RED)) assertDynamicForm(Color.GREEN) assertDynamicForm(Color.WITH_SERIALNAME_red) { _, serialized -> assertEquals("red", serialized) } } @Test fun singlePrimitiveValue() { assertDynamicForm("some string") assertDynamicForm(1.toByte()) assertDynamicForm(1.toShort()) assertDynamicForm(1) assertDynamicForm(1.toFloat()) assertDynamicForm(1.toDouble()) assertDynamicForm('c') assertDynamicForm(true) assertDynamicForm(false) assertDynamicForm(1L) val result = Json.encodeToDynamic(String.serializer().nullable, null) assertEquals(null, result) } @Test fun sealed() { // test of sealed class but not polymorphic serialization assertDynamicForm(Sealed.One("one")) } @Test fun withSerialNam() { assertDynamicForm(WithSerialName("something")) { data, serialized -> assertEquals(data.a, serialized.b) } } @Test fun mapWithNullKey() { val serialized = Json.encodeToDynamic( MapSerializer(String.serializer().nullable, Int.serializer()), mapOf(null to 0, "a" to 1) ) assertNotNull(serialized[null], "null key should be present in output") } @Test fun mapWithSimpleKey() { inline fun assertSimpleMapForm(key: T, value: String) { assertDynamicForm(mapOf(key to value), MapSerializer(serializer(), String.serializer())) } assertSimpleMapForm(1, "Int 1") assertSimpleMapForm("s", "String s") assertSimpleMapForm('c', "char c") assertSimpleMapForm(2.toByte(), "Byte 2") assertSimpleMapForm(3.toShort(), "Short 3") assertSimpleMapForm(4.toLong(), "Long 4") assertSimpleMapForm(5.toFloat(), "Float 5") assertSimpleMapForm(6.toDouble(), "Double 6") assertDynamicForm( mapOf( Color.RED to "RED", Color.GREEN to "GREEN", Color.WITH_SERIALNAME_red to "red" ), MapSerializer(Color.serializer(), String.serializer()) ) { _, serialized -> assertNotNull(serialized["red"], "WITH_SERIALNAME_red should be serialized as 'red'") } } @Test fun mapWithIllegalKey() { val exception = assertFails { Json.encodeToDynamic( MapSerializer(Data.serializer(), String.serializer()), mapOf( Data(1) to "data", Data(2) to "data", Data(3) to "data" ) ) } assertEquals(IllegalArgumentException::class, exception::class) assertTrue("should have a helpful error message") { exception.message?.contains("can't be used in json as map key") == true } assertFails { @Suppress("CAST_NEVER_SUCCEEDS") assertDynamicForm( mapOf( (null as? Data) to "Data null" ), MapSerializer(Data.serializer().nullable, String.serializer()) ) } val doubleSerializer = MapSerializer(Double.serializer(), String.serializer()) val value = mapOf(0.5 to "0.5") var ex = assertFails { assertDynamicForm(value, doubleSerializer) } assertTrue("should have a helpful error message") { ex.message?.contains("can't be used in json as map key") == true } ex = assertFails { assertDynamicForm(mapOf(Double.NaN to "NaN"), doubleSerializer) } assertTrue("should have a helpful error message") { ex.message?.contains("can't be used in json as map key") == true } ex = assertFails { assertDynamicForm(mapOf(Double.NEGATIVE_INFINITY to "NaN"), doubleSerializer) } assertTrue("should have a helpful error message") { ex.message?.contains("can't be used in json as map key") == true } assertDynamicForm(mapOf(11.0 to "11"), doubleSerializer) } @Test fun customSerializerTest() { assertDynamicForm(MyFancyClass("apple"), MyFancyClass.serializer()) { _, serialized -> assertEquals("fancy apple", serialized) } assertDynamicForm( mapOf(MyFancyClass("apple") to "value"), MapSerializer(MyFancyClass.serializer(), String.serializer()) ) { _, serialized -> assertNotNull(serialized["fancy apple"], "should contain key 'fancy apple'") } } } public inline fun assertDynamicForm( data: T, serializer: KSerializer = EmptySerializersModule().serializer(), skipEqualsCheck: Boolean = false, noinline assertions: ((T, dynamic) -> Unit)? = null ) { val serialized = Json.encodeToDynamic(serializer, data) assertions?.invoke(data, serialized) val string = Json.encodeToString(serializer, data) assertEquals( string, JSON.stringify(serialized), "JSON.stringify representation must be the same" ) if (skipEqualsCheck) return // arrays etc. assertEquals(data, Json.decodeFromString(serializer, string)) }