1 /*
<lambda>null2 * Copyright 2017-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
3 */
4
5 package kotlinx.serialization.json
6
7 import kotlinx.serialization.builtins.*
8 import kotlinx.serialization.descriptors.*
9 import kotlinx.serialization.encoding.*
10 import kotlinx.serialization.json.internal.*
11 import kotlinx.serialization.*
12 import kotlinx.serialization.modules.*
13 import kotlin.test.*
14
15 @Suppress("UnsafeCastFromDynamic")
16 class EncodeToDynamicTest {
17 @Serializable
18 data class Data(val a: Int)
19
20 @Serializable
21 open class DataWrapper(open val s: String, val d: Data? = Data(1)) {
22 override fun equals(other: Any?): Boolean {
23 if (this === other) return true
24 if (other == null || this::class.js != other::class.js) return false
25
26 other as DataWrapper
27
28 if (s != other.s) return false
29 if (d != other.d) return false
30
31 return true
32 }
33
34 override fun hashCode(): Int {
35 var result = s.hashCode()
36 result = 31 * result + (d?.hashCode() ?: 0)
37 return result
38 }
39 }
40
41 @Serializable
42 data class NestedList(val a: String, val list: List<Int>)
43
44 @Serializable
45 data class ListOfLists(val l: List<List<Data>>)
46
47 @Serializable
48 data class MapWrapper(val m: Map<String?, Int>)
49
50 @Serializable
51 data class ComplexMapWrapper(val m: Map<String, Data>)
52
53 @Serializable
54 data class WithChar(val a: Char)
55
56 @Serializable
57 data class WithLong(val l: Long)
58
59 @Serializable
60 data class AllTypes(
61 val b: Byte,
62 val s: Short,
63 val i: Int,
64 val f: Float,
65 val d: Double,
66 val c: Char,
67 val B: Boolean,
68 val S: String
69 )
70
71 @Serializable
72 data class EnumWrapper(val e: Color)
73
74 @Serializable
75 sealed class Sealed {
76 @Serializable
77 data class One(val string: String) : Sealed()
78 }
79
80 @Serializable
81 class WithJsName(@JsName("b") val a: String)
82
83 @Serializable
84 data class WithSerialName(@SerialName("b") val a: String)
85
86 @Serializable
87 enum class Color {
88 RED,
89 GREEN,
90
91 @SerialName("red")
92 WITH_SERIALNAME_red
93 }
94
95 @Serializable(MyFancyClass.Companion::class)
96 data class MyFancyClass(val value: String) {
97
98 companion object : KSerializer<MyFancyClass> {
99
100 override val descriptor: SerialDescriptor = PrimitiveSerialDescriptor("MyFancyClass", PrimitiveKind.STRING)
101 override fun serialize(encoder: Encoder, value: MyFancyClass) {
102 encoder.encodeString("fancy ${value.value}")
103 }
104
105 override fun deserialize(decoder: Decoder): MyFancyClass {
106 return MyFancyClass(decoder.decodeString().removePrefix("fancy "))
107 }
108 }
109 }
110
111 @Test
112 fun dynamicSimpleTest() {
113 assertDynamicForm(Data(42)) { data, serialized ->
114 assertEquals(data.a, serialized.a)
115 }
116
117 assertDynamicForm(WithChar('c')) { data, serialized ->
118 assertEquals(data.a.toString(), serialized.a)
119 }
120
121 assertDynamicForm(AllTypes(1, 2, 3, 4.0f, 5.0, 'c', true, "string"))
122
123
124 assertDynamicForm(WithLong(5L))
125 assertDynamicForm(WithLong(MAX_SAFE_INTEGER.toLong()))
126 assertDynamicForm(WithLong(MAX_SAFE_INTEGER.unaryMinus().toLong()))
127 }
128
129 @Test
130 fun wrappedObjectsTest() {
131 assertDynamicForm(DataWrapper("a string", Data(42))) { data, serialized ->
132 assertEquals(data.s, serialized.s)
133 assertNotNull(serialized.d)
134 assertEquals(data.d?.a, serialized.d.a)
135 }
136 }
137
138 @Test
139 fun listTest() {
140 assertDynamicForm(listOf(1, 2, 3, 44), serializer = ListSerializer(Int.serializer())) { data, serialized ->
141 assertNotNull(serialized.length, "length property should exist")
142 assertEquals(data.size, serialized.length)
143
144 for (i in data.indices) {
145 assertEquals(data[i], serialized[i])
146 }
147 }
148 }
149
150 @Test
151 fun arrayTest() {
152 assertDynamicForm(intArrayOf(1, 2, 3, 44), serializer = IntArraySerializer(), true) { data, serialized ->
153 assertNotNull(serialized.length, "length property should exist")
154 assertEquals(data.size, serialized.length)
155
156 for (i in data.indices) {
157 assertEquals(data[i], serialized[i])
158 }
159 }
160 }
161
162 @Test
163 fun nestedListTest() {
164 assertDynamicForm(NestedList("a string", listOf(1, 2, 3, 44))) { data, serialized ->
165 assertEquals(data.a, serialized.a)
166 assertNotNull(serialized.list.length, "length property should exist")
167 assertEquals(data.list.size, serialized.list.length)
168
169 for (i in data.list.indices) {
170 assertEquals(data.list[i], serialized.list[i])
171 }
172 }
173
174 }
175
176 @Test
177 fun complexMapWrapperTest() {
178 assertDynamicForm(ComplexMapWrapper(mapOf("key1" to Data(1), "key2" to Data(2))))
179 }
180
181 @Test
182 fun mapWrapperTest() {
183 assertDynamicForm(MapWrapper(mapOf("key1" to 1, "key2" to 2)))
184 }
185
186 @Test
187 fun listOfListsTest() {
188 assertDynamicForm(
189 ListOfLists(
190 listOf(
191 listOf(Data(11), Data(12), Data(13)),
192 listOf(Data(21), Data(22))
193 )
194 )
195 ) { data, serialized ->
196 assertEquals(data.l.size, serialized.l.length)
197 assertEquals(data.l.first().size, serialized.l[0].length)
198 }
199 }
200
201 @Serializable
202 data class NestedCollections(val data: Map<String, Map<String, List<Int>>>)
203
204 @Test
205 fun nestedCollections() {
206 assertDynamicForm(
207 NestedCollections(
208 mapOf(
209 "one" to mapOf("oneone" to listOf(11, 12, 13), "onetwo" to listOf(1)),
210 "two" to mapOf("twotwo" to listOf(22, 23))
211 )
212 )
213 , serializer = NestedCollections.serializer()
214 )
215 }
216
217 @Test
218 fun enums() {
219 assertDynamicForm(EnumWrapper(Color.RED))
220 assertDynamicForm(Color.GREEN)
221 assertDynamicForm(Color.WITH_SERIALNAME_red) { _, serialized ->
222 assertEquals("red", serialized)
223 }
224 }
225
226 @Test
227 fun singlePrimitiveValue() {
228 assertDynamicForm("some string")
229 assertDynamicForm(1.toByte())
230 assertDynamicForm(1.toShort())
231 assertDynamicForm(1)
232 assertDynamicForm(1.toFloat())
233 assertDynamicForm(1.toDouble())
234 assertDynamicForm('c')
235 assertDynamicForm(true)
236 assertDynamicForm(false)
237 assertDynamicForm(1L)
238 val result = Json.encodeToDynamic(String.serializer().nullable, null)
239 assertEquals(null, result)
240 }
241
242 @Test
243 fun sealed() {
244 // test of sealed class but not polymorphic serialization
245 assertDynamicForm(Sealed.One("one"))
246 }
247
248 @Test
249 fun withSerialNam() {
250 assertDynamicForm(WithSerialName("something")) { data, serialized ->
251 assertEquals(data.a, serialized.b)
252 }
253 }
254
255 @Test
256 fun mapWithNullKey() {
257 val serialized = Json.encodeToDynamic(
258 MapSerializer(String.serializer().nullable, Int.serializer()),
259 mapOf(null to 0, "a" to 1)
260 )
261 assertNotNull(serialized[null], "null key should be present in output")
262 }
263
264 @Test
265 fun mapWithSimpleKey() {
266
267 inline fun <reified T> assertSimpleMapForm(key: T, value: String) {
268 assertDynamicForm(mapOf(key to value), MapSerializer(serializer(), String.serializer()))
269 }
270
271 assertSimpleMapForm(1, "Int 1")
272 assertSimpleMapForm("s", "String s")
273 assertSimpleMapForm('c', "char c")
274 assertSimpleMapForm(2.toByte(), "Byte 2")
275 assertSimpleMapForm(3.toShort(), "Short 3")
276 assertSimpleMapForm(4.toLong(), "Long 4")
277 assertSimpleMapForm(5.toFloat(), "Float 5")
278 assertSimpleMapForm(6.toDouble(), "Double 6")
279
280 assertDynamicForm(
281 mapOf(
282 Color.RED to "RED",
283 Color.GREEN to "GREEN",
284 Color.WITH_SERIALNAME_red to "red"
285 ),
286 MapSerializer(Color.serializer(), String.serializer())
287 ) { _, serialized ->
288 assertNotNull(serialized["red"], "WITH_SERIALNAME_red should be serialized as 'red'")
289 }
290 }
291
292 @Test
293 fun mapWithIllegalKey() {
294
295 val exception = assertFails {
296 Json.encodeToDynamic(
297 MapSerializer(Data.serializer(), String.serializer()),
298 mapOf(
299 Data(1) to "data",
300 Data(2) to "data",
301 Data(3) to "data"
302 )
303 )
304 }
305 assertEquals(IllegalArgumentException::class, exception::class)
306 assertTrue("should have a helpful error message") {
307 exception.message?.contains("can't be used in json as map key") == true
308 }
309
310 assertFails {
311 @Suppress("CAST_NEVER_SUCCEEDS")
312 assertDynamicForm(
313 mapOf(
314 (null as? Data) to "Data null"
315 ),
316 MapSerializer(Data.serializer().nullable, String.serializer())
317 )
318 }
319
320
321 val doubleSerializer = MapSerializer(Double.serializer(), String.serializer())
322 val value = mapOf(0.5 to "0.5")
323 var ex = assertFails {
324 assertDynamicForm(value, doubleSerializer)
325 }
326 assertTrue("should have a helpful error message") {
327 ex.message?.contains("can't be used in json as map key") == true
328 }
329
330 ex = assertFails {
331 assertDynamicForm(mapOf(Double.NaN to "NaN"), doubleSerializer)
332 }
333 assertTrue("should have a helpful error message") {
334 ex.message?.contains("can't be used in json as map key") == true
335 }
336
337 ex = assertFails {
338 assertDynamicForm(mapOf(Double.NEGATIVE_INFINITY to "NaN"), doubleSerializer)
339 }
340 assertTrue("should have a helpful error message") {
341 ex.message?.contains("can't be used in json as map key") == true
342 }
343
344 assertDynamicForm(mapOf(11.0 to "11"), doubleSerializer)
345
346 }
347
348 @Test
349 fun customSerializerTest() {
350 assertDynamicForm(MyFancyClass("apple"), MyFancyClass.serializer()) { _, serialized ->
351 assertEquals("fancy apple", serialized)
352 }
353
354 assertDynamicForm(
355 mapOf(MyFancyClass("apple") to "value"),
356 MapSerializer(MyFancyClass.serializer(), String.serializer())
357 ) { _, serialized ->
358 assertNotNull(serialized["fancy apple"], "should contain key 'fancy apple'")
359 }
360 }
361 }
362
assertDynamicFormnull363 public inline fun <reified T : Any> assertDynamicForm(
364 data: T,
365 serializer: KSerializer<T> = EmptySerializersModule().serializer(),
366 skipEqualsCheck: Boolean = false,
367 noinline assertions: ((T, dynamic) -> Unit)? = null
368 ) {
369 val serialized = Json.encodeToDynamic(serializer, data)
370 assertions?.invoke(data, serialized)
371 val string = Json.encodeToString(serializer, data)
372 assertEquals(
373 string,
374 JSON.stringify(serialized),
375 "JSON.stringify representation must be the same"
376 )
377
378 if (skipEqualsCheck) return // arrays etc.
379 assertEquals(data, Json.decodeFromString(serializer, string))
380 }
381