xref: /aosp_15_r20/external/moshi/kotlin/tests/src/test/kotlin/com/squareup/moshi/kotlin/reflect/KotlinJsonAdapterTest.kt (revision 238ab3e782f339ab327592a602fa7df0a3f729ad)
1 /*
<lambda>null2  * Copyright (C) 2017 Square, Inc.
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *    https://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 package com.squareup.moshi.kotlin.reflect
17 
18 import com.google.common.truth.Truth.assertThat
19 import com.squareup.moshi.FromJson
20 import com.squareup.moshi.Json
21 import com.squareup.moshi.JsonAdapter
22 import com.squareup.moshi.JsonClass
23 import com.squareup.moshi.JsonDataException
24 import com.squareup.moshi.JsonQualifier
25 import com.squareup.moshi.JsonReader
26 import com.squareup.moshi.JsonWriter
27 import com.squareup.moshi.Moshi
28 import com.squareup.moshi.ToJson
29 import com.squareup.moshi.adapter
30 import org.assertj.core.api.Assertions
31 import org.junit.Assert.fail
32 import org.junit.Test
33 import java.io.ByteArrayOutputStream
34 import java.lang.reflect.ParameterizedType
35 import java.lang.reflect.WildcardType
36 import java.util.Locale
37 import java.util.SimpleTimeZone
38 import kotlin.annotation.AnnotationRetention.RUNTIME
39 
40 @Suppress("UNUSED", "UNUSED_PARAMETER")
41 class KotlinJsonAdapterTest {
42   @Test fun constructorParameters() {
43     val moshi = Moshi.Builder().add(KotlinJsonAdapterFactory()).build()
44     val jsonAdapter = moshi.adapter<ConstructorParameters>()
45 
46     val encoded = ConstructorParameters(3, 5)
47     assertThat(jsonAdapter.toJson(encoded)).isEqualTo("""{"a":3,"b":5}""")
48 
49     val decoded = jsonAdapter.fromJson("""{"a":4,"b":6}""")!!
50     assertThat(decoded.a).isEqualTo(4)
51     assertThat(decoded.b).isEqualTo(6)
52   }
53 
54   class ConstructorParameters(var a: Int, var b: Int)
55 
56   @Test fun properties() {
57     val moshi = Moshi.Builder().add(KotlinJsonAdapterFactory()).build()
58     val jsonAdapter = moshi.adapter<Properties>()
59 
60     val encoded = Properties()
61     encoded.a = 3
62     encoded.b = 5
63     assertThat(jsonAdapter.toJson(encoded)).isEqualTo("""{"a":3,"b":5}""")
64 
65     val decoded = jsonAdapter.fromJson("""{"a":3,"b":5}""")!!
66     assertThat(decoded.a).isEqualTo(3)
67     assertThat(decoded.b).isEqualTo(5)
68   }
69 
70   class Properties {
71     var a: Int = -1
72     var b: Int = -1
73   }
74 
75   @Test fun constructorParametersAndProperties() {
76     val moshi = Moshi.Builder().add(KotlinJsonAdapterFactory()).build()
77     val jsonAdapter = moshi.adapter<ConstructorParametersAndProperties>()
78 
79     val encoded = ConstructorParametersAndProperties(3)
80     encoded.b = 5
81     assertThat(jsonAdapter.toJson(encoded)).isEqualTo("""{"a":3,"b":5}""")
82 
83     val decoded = jsonAdapter.fromJson("""{"a":4,"b":6}""")!!
84     assertThat(decoded.a).isEqualTo(4)
85     assertThat(decoded.b).isEqualTo(6)
86   }
87 
88   class ConstructorParametersAndProperties(var a: Int) {
89     var b: Int = -1
90   }
91 
92   @Test fun immutableConstructorParameters() {
93     val moshi = Moshi.Builder().add(KotlinJsonAdapterFactory()).build()
94     val jsonAdapter = moshi.adapter<ImmutableConstructorParameters>()
95 
96     val encoded = ImmutableConstructorParameters(3, 5)
97     assertThat(jsonAdapter.toJson(encoded)).isEqualTo("""{"a":3,"b":5}""")
98 
99     val decoded = jsonAdapter.fromJson("""{"a":4,"b":6}""")!!
100     assertThat(decoded.a).isEqualTo(4)
101     assertThat(decoded.b).isEqualTo(6)
102   }
103 
104   class ImmutableConstructorParameters(val a: Int, val b: Int)
105 
106   @Test fun immutableProperties() {
107     val moshi = Moshi.Builder().add(KotlinJsonAdapterFactory()).build()
108     val jsonAdapter = moshi.adapter<ImmutableProperties>()
109 
110     val encoded = ImmutableProperties(3, 5)
111     assertThat(jsonAdapter.toJson(encoded)).isEqualTo("""{"a":3,"b":5}""")
112 
113     val decoded = jsonAdapter.fromJson("""{"a":3,"b":5}""")!!
114     assertThat(decoded.a).isEqualTo(3)
115     assertThat(decoded.b).isEqualTo(5)
116   }
117 
118   class ImmutableProperties(a: Int, b: Int) {
119     val a = a
120     val b = b
121   }
122 
123   @Test fun constructorDefaults() {
124     val moshi = Moshi.Builder().add(KotlinJsonAdapterFactory()).build()
125     val jsonAdapter = moshi.adapter<ConstructorDefaultValues>()
126 
127     val encoded = ConstructorDefaultValues(3, 5)
128     assertThat(jsonAdapter.toJson(encoded)).isEqualTo("""{"a":3,"b":5}""")
129 
130     val decoded = jsonAdapter.fromJson("""{"b":6}""")!!
131     assertThat(decoded.a).isEqualTo(-1)
132     assertThat(decoded.b).isEqualTo(6)
133   }
134 
135   class ConstructorDefaultValues(var a: Int = -1, var b: Int = -2)
136 
137   @Test fun duplicatedValueParameter() {
138     val moshi = Moshi.Builder().add(KotlinJsonAdapterFactory()).build()
139     val jsonAdapter = moshi.adapter<DuplicateValueParameter>()
140 
141     try {
142       jsonAdapter.fromJson("""{"a":4,"a":4}""")
143       fail()
144     } catch (expected: JsonDataException) {
145       assertThat(expected).hasMessageThat().isEqualTo("Multiple values for 'a' at $.a")
146     }
147   }
148 
149   class DuplicateValueParameter(var a: Int = -1, var b: Int = -2)
150 
151   @Test fun duplicatedValueProperty() {
152     val moshi = Moshi.Builder().add(KotlinJsonAdapterFactory()).build()
153     val jsonAdapter = moshi.adapter<DuplicateValueProperty>()
154 
155     try {
156       jsonAdapter.fromJson("""{"a":4,"a":4}""")
157       fail()
158     } catch (expected: JsonDataException) {
159       assertThat(expected).hasMessageThat().isEqualTo("Multiple values for 'a' at $.a")
160     }
161   }
162 
163   class DuplicateValueProperty {
164     var a: Int = -1
165     var b: Int = -2
166   }
167 
168   @Test fun explicitNull() {
169     val moshi = Moshi.Builder().add(KotlinJsonAdapterFactory()).build()
170     val jsonAdapter = moshi.adapter<ExplicitNull>()
171 
172     val encoded = ExplicitNull(null, 5)
173     assertThat(jsonAdapter.toJson(encoded)).isEqualTo("""{"b":5}""")
174     assertThat(jsonAdapter.serializeNulls().toJson(encoded)).isEqualTo("""{"a":null,"b":5}""")
175 
176     val decoded = jsonAdapter.fromJson("""{"a":null,"b":6}""")!!
177     assertThat(decoded.a).isEqualTo(null)
178     assertThat(decoded.b).isEqualTo(6)
179   }
180 
181   class ExplicitNull(var a: Int?, var b: Int?)
182 
183   @Test fun absentNull() {
184     val moshi = Moshi.Builder().add(KotlinJsonAdapterFactory()).build()
185     val jsonAdapter = moshi.adapter<AbsentNull>()
186 
187     val encoded = AbsentNull(null, 5)
188     assertThat(jsonAdapter.toJson(encoded)).isEqualTo("""{"b":5}""")
189     assertThat(jsonAdapter.serializeNulls().toJson(encoded)).isEqualTo("""{"a":null,"b":5}""")
190 
191     val decoded = jsonAdapter.fromJson("""{"b":6}""")!!
192     assertThat(decoded.a).isNull()
193     assertThat(decoded.b).isEqualTo(6)
194   }
195 
196   class AbsentNull(var a: Int?, var b: Int?)
197 
198   @Test fun repeatedValue() {
199     val moshi = Moshi.Builder().add(KotlinJsonAdapterFactory()).build()
200     val jsonAdapter = moshi.adapter<RepeatedValue>()
201 
202     try {
203       jsonAdapter.fromJson("""{"a":4,"b":null,"b":6}""")
204       fail()
205     } catch (expected: JsonDataException) {
206       assertThat(expected).hasMessageThat().isEqualTo("Multiple values for 'b' at $.b")
207     }
208   }
209 
210   class RepeatedValue(var a: Int, var b: Int?)
211 
212   @Test fun constructorParameterWithQualifier() {
213     val moshi = Moshi.Builder()
214       .add(KotlinJsonAdapterFactory())
215       .add(UppercaseJsonAdapter())
216       .build()
217     val jsonAdapter = moshi.adapter<ConstructorParameterWithQualifier>()
218 
219     val encoded = ConstructorParameterWithQualifier("Android", "Banana")
220     assertThat(jsonAdapter.toJson(encoded)).isEqualTo("""{"a":"ANDROID","b":"Banana"}""")
221 
222     val decoded = jsonAdapter.fromJson("""{"a":"Android","b":"Banana"}""")!!
223     assertThat(decoded.a).isEqualTo("android")
224     assertThat(decoded.b).isEqualTo("Banana")
225   }
226 
227   class ConstructorParameterWithQualifier(@Uppercase var a: String, var b: String)
228 
229   @Test fun propertyWithQualifier() {
230     val moshi = Moshi.Builder()
231       .add(KotlinJsonAdapterFactory())
232       .add(UppercaseJsonAdapter())
233       .build()
234     val jsonAdapter = moshi.adapter<PropertyWithQualifier>()
235 
236     val encoded = PropertyWithQualifier()
237     encoded.a = "Android"
238     encoded.b = "Banana"
239     assertThat(jsonAdapter.toJson(encoded)).isEqualTo("""{"a":"ANDROID","b":"Banana"}""")
240 
241     val decoded = jsonAdapter.fromJson("""{"a":"Android","b":"Banana"}""")!!
242     assertThat(decoded.a).isEqualTo("android")
243     assertThat(decoded.b).isEqualTo("Banana")
244   }
245 
246   class PropertyWithQualifier {
247     @Uppercase var a: String = ""
248     var b: String = ""
249   }
250 
251   @Test fun constructorParameterWithJsonName() {
252     val moshi = Moshi.Builder().add(KotlinJsonAdapterFactory()).build()
253     val jsonAdapter = moshi.adapter<ConstructorParameterWithJsonName>()
254 
255     val encoded = ConstructorParameterWithJsonName(3, 5)
256     assertThat(jsonAdapter.toJson(encoded)).isEqualTo("""{"key a":3,"b":5}""")
257 
258     val decoded = jsonAdapter.fromJson("""{"key a":4,"b":6}""")!!
259     assertThat(decoded.a).isEqualTo(4)
260     assertThat(decoded.b).isEqualTo(6)
261   }
262 
263   class ConstructorParameterWithJsonName(@Json(name = "key a") var a: Int, var b: Int)
264 
265   @Test fun propertyWithJsonName() {
266     val moshi = Moshi.Builder().add(KotlinJsonAdapterFactory()).build()
267     val jsonAdapter = moshi.adapter<PropertyWithJsonName>()
268 
269     val encoded = PropertyWithJsonName()
270     encoded.a = 3
271     encoded.b = 5
272     assertThat(jsonAdapter.toJson(encoded)).isEqualTo("""{"key a":3,"b":5}""")
273 
274     val decoded = jsonAdapter.fromJson("""{"key a":4,"b":6}""")!!
275     assertThat(decoded.a).isEqualTo(4)
276     assertThat(decoded.b).isEqualTo(6)
277   }
278 
279   class PropertyWithJsonName {
280     @Json(name = "key a") var a: Int = -1
281     var b: Int = -1
282   }
283 
284   @Test fun requiredTransientConstructorParameterFails() {
285     val moshi = Moshi.Builder().add(KotlinJsonAdapterFactory()).build()
286     try {
287       moshi.adapter<RequiredTransientConstructorParameter>()
288       fail()
289     } catch (expected: IllegalArgumentException) {
290       assertThat(expected).hasMessageThat().isEqualTo(
291         "No default value for transient constructor parameter #0 " +
292           "a of fun `<init>`(kotlin.Int): " +
293           "com.squareup.moshi.kotlin.reflect.KotlinJsonAdapterTest.RequiredTransientConstructorParameter"
294       )
295     }
296   }
297 
298   class RequiredTransientConstructorParameter(@Transient var a: Int)
299 
300   @Test fun requiredIgnoredConstructorParameterFails() {
301     val moshi = Moshi.Builder().add(KotlinJsonAdapterFactory()).build()
302     try {
303       moshi.adapter<RequiredIgnoredConstructorParameter>()
304       fail()
305     } catch (expected: IllegalArgumentException) {
306       assertThat(expected).hasMessageThat().isEqualTo(
307         "No default value for ignored constructor parameter #0 " +
308           "a of fun `<init>`(kotlin.Int): " +
309           "com.squareup.moshi.kotlin.reflect.KotlinJsonAdapterTest.RequiredIgnoredConstructorParameter"
310       )
311     }
312   }
313 
314   class RequiredIgnoredConstructorParameter(@Json(ignore = true) var a: Int)
315 
316   @Test fun constructorParametersAndPropertiesWithSameNamesMustHaveSameTypes() {
317     val moshi = Moshi.Builder().add(KotlinJsonAdapterFactory()).build()
318     try {
319       moshi.adapter<ConstructorParameterWithSameNameAsPropertyButDifferentType>()
320       fail()
321     } catch (expected: IllegalArgumentException) {
322       assertThat(expected).hasMessageThat().isEqualTo(
323         "'a' has a constructor parameter of type " +
324           "kotlin.Int but a property of type kotlin.String."
325       )
326     }
327   }
328 
329   class ConstructorParameterWithSameNameAsPropertyButDifferentType(a: Int) {
330     var a = "boo"
331   }
332 
333   @Test fun supertypeConstructorParameters() {
334     val moshi = Moshi.Builder().add(KotlinJsonAdapterFactory()).build()
335     val jsonAdapter = moshi.adapter<SubtypeConstructorParameters>()
336 
337     val encoded = SubtypeConstructorParameters(3, 5)
338     assertThat(jsonAdapter.toJson(encoded)).isEqualTo("""{"a":3,"b":5}""")
339 
340     val decoded = jsonAdapter.fromJson("""{"a":4,"b":6}""")!!
341     assertThat(decoded.a).isEqualTo(4)
342     assertThat(decoded.b).isEqualTo(6)
343   }
344 
345   open class SupertypeConstructorParameters(var a: Int)
346 
347   class SubtypeConstructorParameters(a: Int, var b: Int) : SupertypeConstructorParameters(a)
348 
349   @Test fun supertypeProperties() {
350     val moshi = Moshi.Builder().add(KotlinJsonAdapterFactory()).build()
351     val jsonAdapter = moshi.adapter<SubtypeProperties>()
352 
353     val encoded = SubtypeProperties()
354     encoded.a = 3
355     encoded.b = 5
356     assertThat(jsonAdapter.toJson(encoded)).isEqualTo("""{"b":5,"a":3}""")
357 
358     val decoded = jsonAdapter.fromJson("""{"a":4,"b":6}""")!!
359     assertThat(decoded.a).isEqualTo(4)
360     assertThat(decoded.b).isEqualTo(6)
361   }
362 
363   open class SupertypeProperties {
364     var a: Int = -1
365   }
366 
367   class SubtypeProperties : SupertypeProperties() {
368     var b: Int = -1
369   }
370 
371   @Test fun extendsPlatformClassWithPrivateField() {
372     val moshi = Moshi.Builder().add(KotlinJsonAdapterFactory()).build()
373     val jsonAdapter = moshi.adapter<ExtendsPlatformClassWithPrivateField>()
374 
375     val encoded = ExtendsPlatformClassWithPrivateField(3)
376     assertThat(jsonAdapter.toJson(encoded)).isEqualTo("""{"a":3}""")
377 
378     val decoded = jsonAdapter.fromJson("""{"a":4,"id":"B"}""")!!
379     assertThat(decoded.a).isEqualTo(4)
380     assertThat(decoded.id).isEqualTo("C")
381   }
382 
383   internal class ExtendsPlatformClassWithPrivateField(var a: Int) : SimpleTimeZone(0, "C")
384 
385   @Test fun extendsPlatformClassWithProtectedField() {
386     val moshi = Moshi.Builder().add(KotlinJsonAdapterFactory()).build()
387     val jsonAdapter = moshi.adapter<ExtendsPlatformClassWithProtectedField>()
388 
389     val encoded = ExtendsPlatformClassWithProtectedField(3)
390     assertThat(jsonAdapter.toJson(encoded)).isEqualTo("""{"a":3,"buf":[0,0],"count":0}""")
391 
392     val decoded = jsonAdapter.fromJson("""{"a":4,"buf":[0,0],"size":0}""")!!
393     assertThat(decoded.a).isEqualTo(4)
394     assertThat(decoded.buf()).isEqualTo(ByteArray(2) { 0 })
395     assertThat(decoded.count()).isEqualTo(0)
396   }
397 
398   internal class ExtendsPlatformClassWithProtectedField(var a: Int) : ByteArrayOutputStream(2) {
399     fun buf(): ByteArray = buf
400     fun count(): Int = count
401   }
402 
403   @Test fun platformTypeThrows() {
404     val moshi = Moshi.Builder().add(KotlinJsonAdapterFactory()).build()
405     try {
406       moshi.adapter<Triple<*, *, *>>()
407       fail()
408     } catch (e: IllegalArgumentException) {
409       assertThat(e).hasMessageThat().isEqualTo(
410         "Platform class kotlin.Triple in kotlin.Triple<?, ?, ?> requires explicit JsonAdapter to be registered"
411       )
412     }
413   }
414 
415   @Test fun privateConstructorParameters() {
416     val moshi = Moshi.Builder().add(KotlinJsonAdapterFactory()).build()
417     val jsonAdapter = moshi.adapter<PrivateConstructorParameters>()
418 
419     val encoded = PrivateConstructorParameters(3, 5)
420     assertThat(jsonAdapter.toJson(encoded)).isEqualTo("""{"a":3,"b":5}""")
421 
422     val decoded = jsonAdapter.fromJson("""{"a":4,"b":6}""")!!
423     assertThat(decoded.a()).isEqualTo(4)
424     assertThat(decoded.b()).isEqualTo(6)
425   }
426 
427   class PrivateConstructorParameters(private var a: Int, private var b: Int) {
428     fun a() = a
429     fun b() = b
430   }
431 
432   @Test fun privateConstructor() {
433     val moshi = Moshi.Builder().add(KotlinJsonAdapterFactory()).build()
434     val jsonAdapter = moshi.adapter<PrivateConstructor>()
435 
436     val encoded = PrivateConstructor.newInstance(3, 5)
437     assertThat(jsonAdapter.toJson(encoded)).isEqualTo("""{"a":3,"b":5}""")
438 
439     val decoded = jsonAdapter.fromJson("""{"a":4,"b":6}""")!!
440     assertThat(decoded.a()).isEqualTo(4)
441     assertThat(decoded.b()).isEqualTo(6)
442   }
443 
444   class PrivateConstructor private constructor(var a: Int, var b: Int) {
445     fun a() = a
446     fun b() = b
447     companion object {
448       fun newInstance(a: Int, b: Int) = PrivateConstructor(a, b)
449     }
450   }
451 
452   @Test fun privateProperties() {
453     val moshi = Moshi.Builder().add(KotlinJsonAdapterFactory()).build()
454     val jsonAdapter = moshi.adapter<PrivateProperties>()
455 
456     val encoded = PrivateProperties()
457     encoded.a(3)
458     encoded.b(5)
459     assertThat(jsonAdapter.toJson(encoded)).isEqualTo("""{"a":3,"b":5}""")
460 
461     val decoded = jsonAdapter.fromJson("""{"a":4,"b":6}""")!!
462     assertThat(decoded.a()).isEqualTo(4)
463     assertThat(decoded.b()).isEqualTo(6)
464   }
465 
466   class PrivateProperties {
467     private var a: Int = -1
468     private var b: Int = -1
469 
470     fun a() = a
471 
472     fun a(a: Int) {
473       this.a = a
474     }
475 
476     fun b() = b
477 
478     fun b(b: Int) {
479       this.b = b
480     }
481   }
482 
483   @Test fun unsettablePropertyIgnored() {
484     val moshi = Moshi.Builder().add(KotlinJsonAdapterFactory()).build()
485     val jsonAdapter = moshi.adapter<UnsettableProperty>()
486 
487     val encoded = UnsettableProperty()
488     encoded.b = 5
489     assertThat(jsonAdapter.toJson(encoded)).isEqualTo("""{"b":5}""")
490 
491     val decoded = jsonAdapter.fromJson("""{"a":4,"b":6}""")!!
492     assertThat(decoded.a).isEqualTo(-1)
493     assertThat(decoded.b).isEqualTo(6)
494   }
495 
496   class UnsettableProperty {
497     val a: Int = -1
498     var b: Int = -1
499   }
500 
501   @Test fun getterOnlyNoBackingField() {
502     val moshi = Moshi.Builder().add(KotlinJsonAdapterFactory()).build()
503     val jsonAdapter = moshi.adapter<GetterOnly>()
504 
505     val encoded = GetterOnly(3, 5)
506     assertThat(jsonAdapter.toJson(encoded)).isEqualTo("""{"a":3,"b":5}""")
507 
508     val decoded = jsonAdapter.fromJson("""{"a":4,"b":6}""")!!
509     assertThat(decoded.a).isEqualTo(4)
510     assertThat(decoded.b).isEqualTo(6)
511     assertThat(decoded.total).isEqualTo(10)
512   }
513 
514   class GetterOnly(var a: Int, var b: Int) {
515     val total: Int
516       get() = a + b
517   }
518 
519   @Test fun getterAndSetterNoBackingField() {
520     val moshi = Moshi.Builder().add(KotlinJsonAdapterFactory()).build()
521     val jsonAdapter = moshi.adapter<GetterAndSetter>()
522 
523     val encoded = GetterAndSetter(3, 5)
524     assertThat(jsonAdapter.toJson(encoded)).isEqualTo("""{"a":3,"b":5,"total":8}""")
525 
526     // Whether b is 6 or 7 is an implementation detail. Currently we call constructors then setters.
527     val decoded1 = jsonAdapter.fromJson("""{"a":4,"b":6,"total":11}""")!!
528     assertThat(decoded1.a).isEqualTo(4)
529     assertThat(decoded1.b).isEqualTo(7)
530     assertThat(decoded1.total).isEqualTo(11)
531 
532     // Whether b is 6 or 7 is an implementation detail. Currently we call constructors then setters.
533     val decoded2 = jsonAdapter.fromJson("""{"a":4,"total":11,"b":6}""")!!
534     assertThat(decoded2.a).isEqualTo(4)
535     assertThat(decoded2.b).isEqualTo(7)
536     assertThat(decoded2.total).isEqualTo(11)
537   }
538 
539   class GetterAndSetter(var a: Int, var b: Int) {
540     var total: Int
541       get() = a + b
542       set(value) {
543         b = value - a
544       }
545   }
546 
547   @Test fun nonPropertyConstructorParameter() {
548     val moshi = Moshi.Builder().add(KotlinJsonAdapterFactory()).build()
549     try {
550       moshi.adapter<NonPropertyConstructorParameter>()
551       fail()
552     } catch (expected: IllegalArgumentException) {
553       assertThat(expected).hasMessageThat().isEqualTo(
554         "No property for required constructor parameter #0 a of fun `<init>`(" +
555           "kotlin.Int, kotlin.Int): ${NonPropertyConstructorParameter::class.qualifiedName}"
556       )
557     }
558   }
559 
560   class NonPropertyConstructorParameter(a: Int, val b: Int)
561 
562   @Test fun kotlinEnumsAreNotCovered() {
563     val moshi = Moshi.Builder().add(KotlinJsonAdapterFactory()).build()
564     val adapter = moshi.adapter<UsingEnum>()
565 
566     assertThat(adapter.fromJson("""{"e": "A"}""")).isEqualTo(UsingEnum(KotlinEnum.A))
567   }
568 
569   data class UsingEnum(val e: KotlinEnum)
570 
571   enum class KotlinEnum {
572     A, B
573   }
574 
575   @Test fun interfacesNotSupported() {
576     val moshi = Moshi.Builder().add(KotlinJsonAdapterFactory()).build()
577     try {
578       moshi.adapter<Interface>()
579       fail()
580     } catch (e: IllegalArgumentException) {
581       assertThat(e).hasMessageThat().isEqualTo(
582         "No JsonAdapter for interface " +
583           "com.squareup.moshi.kotlin.reflect.KotlinJsonAdapterTest\$Interface (with no annotations)"
584       )
585     }
586   }
587 
588   interface Interface
589 
590   @Test fun abstractClassesNotSupported() {
591     val moshi = Moshi.Builder().add(KotlinJsonAdapterFactory()).build()
592     try {
593       moshi.adapter<AbstractClass>()
594       fail()
595     } catch (e: IllegalArgumentException) {
596       assertThat(e).hasMessageThat().isEqualTo(
597         "Cannot serialize abstract class " +
598           "com.squareup.moshi.kotlin.reflect.KotlinJsonAdapterTest\$AbstractClass"
599       )
600     }
601   }
602 
603   abstract class AbstractClass(val a: Int)
604 
605   @Test fun innerClassesNotSupported() {
606     val moshi = Moshi.Builder().add(KotlinJsonAdapterFactory()).build()
607     try {
608       moshi.adapter<InnerClass>()
609       fail()
610     } catch (e: IllegalArgumentException) {
611       assertThat(e).hasMessageThat().isEqualTo(
612         "Cannot serialize inner class " +
613           "com.squareup.moshi.kotlin.reflect.KotlinJsonAdapterTest\$InnerClass"
614       )
615     }
616   }
617 
618   inner class InnerClass(val a: Int)
619 
620   @Test fun localClassesNotSupported() {
621     class LocalClass(val a: Int)
622     val moshi = Moshi.Builder().add(KotlinJsonAdapterFactory()).build()
623     try {
624       moshi.adapter<LocalClass>()
625       fail()
626     } catch (e: IllegalArgumentException) {
627       assertThat(e).hasMessageThat().isEqualTo(
628         "Cannot serialize local class or object expression " +
629           "com.squareup.moshi.kotlin.reflect.KotlinJsonAdapterTest\$localClassesNotSupported\$LocalClass"
630       )
631     }
632   }
633 
634   @Test fun objectDeclarationsNotSupported() {
635     val moshi = Moshi.Builder().add(KotlinJsonAdapterFactory()).build()
636     try {
637       moshi.adapter<ObjectDeclaration>()
638       fail()
639     } catch (e: IllegalArgumentException) {
640       assertThat(e).hasMessageThat().isEqualTo(
641         "Cannot serialize object declaration " +
642           "com.squareup.moshi.kotlin.reflect.KotlinJsonAdapterTest\$ObjectDeclaration"
643       )
644     }
645   }
646 
647   object ObjectDeclaration {
648     var a = 5
649   }
650 
651   @Test fun anonymousClassesNotSupported() {
652     val expression = object : Any() {
653       var a = 5
654     }
655     val moshi = Moshi.Builder().add(KotlinJsonAdapterFactory()).build()
656     try {
657       moshi.adapter(expression.javaClass)
658       fail()
659     } catch (e: IllegalArgumentException) {
660       // anonymous/local classes are slightly different in bytecode across JVM versions
661       val javaVersion = System.getProperty("java.version")
662       val type = if (javaVersion.startsWith("1.8")) {
663         "local class or object expression"
664       } else {
665         "anonymous class"
666       }
667       assertThat(e).hasMessageThat().isEqualTo(
668         "Cannot serialize $type " +
669           "com.squareup.moshi.kotlin.reflect.KotlinJsonAdapterTest\$anonymousClassesNotSupported" +
670           "\$expression$1"
671       )
672     }
673   }
674 
675   @Test fun manyProperties32() {
676     val moshi = Moshi.Builder().add(KotlinJsonAdapterFactory()).build()
677     val jsonAdapter = moshi.adapter<ManyProperties32>()
678 
679     val encoded = ManyProperties32(
680       101, 102, 103, 104, 105,
681       106, 107, 108, 109, 110,
682       111, 112, 113, 114, 115,
683       116, 117, 118, 119, 120,
684       121, 122, 123, 124, 125,
685       126, 127, 128, 129, 130,
686       131, 132
687     )
688     val json = (
689       """
690         |{
691         |"v01":101,"v02":102,"v03":103,"v04":104,"v05":105,
692         |"v06":106,"v07":107,"v08":108,"v09":109,"v10":110,
693         |"v11":111,"v12":112,"v13":113,"v14":114,"v15":115,
694         |"v16":116,"v17":117,"v18":118,"v19":119,"v20":120,
695         |"v21":121,"v22":122,"v23":123,"v24":124,"v25":125,
696         |"v26":126,"v27":127,"v28":128,"v29":129,"v30":130,
697         |"v31":131,"v32":132
698         |}
699         |"""
700       ).trimMargin().replace("\n", "")
701 
702     assertThat(jsonAdapter.toJson(encoded)).isEqualTo(json)
703 
704     val decoded = jsonAdapter.fromJson(json)!!
705     assertThat(decoded.v01).isEqualTo(101)
706     assertThat(decoded.v32).isEqualTo(132)
707   }
708 
709   class ManyProperties32(
710     var v01: Int,
711     var v02: Int,
712     var v03: Int,
713     var v04: Int,
714     var v05: Int,
715     var v06: Int,
716     var v07: Int,
717     var v08: Int,
718     var v09: Int,
719     var v10: Int,
720     var v11: Int,
721     var v12: Int,
722     var v13: Int,
723     var v14: Int,
724     var v15: Int,
725     var v16: Int,
726     var v17: Int,
727     var v18: Int,
728     var v19: Int,
729     var v20: Int,
730     var v21: Int,
731     var v22: Int,
732     var v23: Int,
733     var v24: Int,
734     var v25: Int,
735     var v26: Int,
736     var v27: Int,
737     var v28: Int,
738     var v29: Int,
739     var v30: Int,
740     var v31: Int,
741     var v32: Int
742   )
743 
744   @Test fun manyProperties33() {
745     val moshi = Moshi.Builder().add(KotlinJsonAdapterFactory()).build()
746     val jsonAdapter = moshi.adapter<ManyProperties33>()
747 
748     val encoded = ManyProperties33(
749       101, 102, 103, 104, 105,
750       106, 107, 108, 109, 110,
751       111, 112, 113, 114, 115,
752       116, 117, 118, 119, 120,
753       121, 122, 123, 124, 125,
754       126, 127, 128, 129, 130,
755       131, 132, 133
756     )
757     val json = (
758       """
759         |{
760         |"v01":101,"v02":102,"v03":103,"v04":104,"v05":105,
761         |"v06":106,"v07":107,"v08":108,"v09":109,"v10":110,
762         |"v11":111,"v12":112,"v13":113,"v14":114,"v15":115,
763         |"v16":116,"v17":117,"v18":118,"v19":119,"v20":120,
764         |"v21":121,"v22":122,"v23":123,"v24":124,"v25":125,
765         |"v26":126,"v27":127,"v28":128,"v29":129,"v30":130,
766         |"v31":131,"v32":132,"v33":133
767         |}
768         |"""
769       ).trimMargin().replace("\n", "")
770 
771     assertThat(jsonAdapter.toJson(encoded)).isEqualTo(json)
772 
773     val decoded = jsonAdapter.fromJson(json)!!
774     assertThat(decoded.v01).isEqualTo(101)
775     assertThat(decoded.v32).isEqualTo(132)
776     assertThat(decoded.v33).isEqualTo(133)
777   }
778 
779   class ManyProperties33(
780     var v01: Int,
781     var v02: Int,
782     var v03: Int,
783     var v04: Int,
784     var v05: Int,
785     var v06: Int,
786     var v07: Int,
787     var v08: Int,
788     var v09: Int,
789     var v10: Int,
790     var v11: Int,
791     var v12: Int,
792     var v13: Int,
793     var v14: Int,
794     var v15: Int,
795     var v16: Int,
796     var v17: Int,
797     var v18: Int,
798     var v19: Int,
799     var v20: Int,
800     var v21: Int,
801     var v22: Int,
802     var v23: Int,
803     var v24: Int,
804     var v25: Int,
805     var v26: Int,
806     var v27: Int,
807     var v28: Int,
808     var v29: Int,
809     var v30: Int,
810     var v31: Int,
811     var v32: Int,
812     var v33: Int
813   )
814 
815   data class Box<out T>(val data: T)
816 
817   @Test fun genericTypes() {
818     val moshi = Moshi.Builder().add(KotlinJsonAdapterFactory()).build()
819     val stringBoxAdapter = moshi.adapter<Box<String>>()
820     assertThat(stringBoxAdapter.fromJson("""{"data":"hello"}""")).isEqualTo(Box("hello"))
821     assertThat(stringBoxAdapter.toJson(Box("hello"))).isEqualTo("""{"data":"hello"}""")
822   }
823 
824   data class NestedGenerics<R, C, out V>(val value: Map<R, Map<C, List<V>>>)
825 
826   @Test fun nestedGenericTypes() {
827     val moshi = Moshi.Builder().add(KotlinJsonAdapterFactory()).build()
828     val adapter = moshi.adapter<NestedGenerics<String, Int, Box<String>>>().indent("  ")
829     val json =
830       """
831       |{
832       |  "value": {
833       |    "hello": {
834       |      "1": [
835       |        {
836       |          "data": " "
837       |        },
838       |        {
839       |          "data": "world!"
840       |        }
841       |      ]
842       |    }
843       |  }
844       |}
845       """.trimMargin()
846     val value = NestedGenerics(mapOf("hello" to mapOf(1 to listOf(Box(" "), Box("world!")))))
847     assertThat(adapter.fromJson(json)).isEqualTo(value)
848     assertThat(adapter.toJson(value)).isEqualTo(json)
849   }
850 
851   @Retention(RUNTIME)
852   @JsonQualifier
853   annotation class Uppercase
854 
855   class UppercaseJsonAdapter {
856     @ToJson
857     fun toJson(@Uppercase s: String): String {
858       return s.uppercase(Locale.US)
859     }
860     @FromJson
861     @Uppercase
862     fun fromJson(s: String): String {
863       return s.lowercase(Locale.US)
864     }
865   }
866 
867   data class HasNullableBoolean(val boolean: Boolean?)
868 
869   @Test fun nullablePrimitivesUseBoxedPrimitiveAdapters() {
870     val moshi = Moshi.Builder()
871       .add(
872         JsonAdapter.Factory { type, _, _ ->
873           if (Boolean::class.javaObjectType == type) {
874             return@Factory object : JsonAdapter<Boolean?>() {
875               override fun fromJson(reader: JsonReader): Boolean? {
876                 if (reader.peek() != JsonReader.Token.BOOLEAN) {
877                   reader.skipValue()
878                   return null
879                 }
880                 return reader.nextBoolean()
881               }
882 
883               override fun toJson(writer: JsonWriter, value: Boolean?) {
884                 writer.value(value)
885               }
886             }
887           }
888           null
889         }
890       )
891       .add(KotlinJsonAdapterFactory())
892       .build()
893     val adapter = moshi.adapter<HasNullableBoolean>().serializeNulls()
894     assertThat(adapter.fromJson("""{"boolean":"not a boolean"}"""))
895       .isEqualTo(HasNullableBoolean(null))
896     assertThat(adapter.toJson(HasNullableBoolean(null))).isEqualTo("""{"boolean":null}""")
897   }
898 
899   @Test fun adaptersAreNullSafe() {
900     val moshi = Moshi.Builder()
901       .add(KotlinJsonAdapterFactory())
902       .build()
903 
904     // TODO in CR: We had to mark this as nullable, vs before the jsonadapter factory would always run
905     val adapter = moshi.adapter<HasNullableBoolean?>()
906     assertThat(adapter.fromJson("null")).isNull()
907     assertThat(adapter.toJson(null)).isEqualTo("null")
908   }
909 
910   @Test fun kotlinClassesWithoutAdapterAreRefused() {
911     val moshi = Moshi.Builder().build()
912     try {
913       moshi.adapter<PlainKotlinClass>()
914       fail("Should not pass here")
915     } catch (e: IllegalArgumentException) {
916       assertThat(e).hasMessageThat().contains("Reflective serialization of Kotlin classes")
917     }
918   }
919 
920   class PlainKotlinClass
921 
922   @Test fun mapOfStringToStandardReflectionWildcards() {
923     mapWildcardsParameterizedTest(
924       MapOfStringToStandardReflection::class.java,
925       """{"map":{"key":"value"}}""",
926       MapOfStringToStandardReflection(mapOf("key" to "value"))
927     )
928   }
929 
930   @JvmSuppressWildcards(suppress = false)
931   data class MapOfStringToStandardReflection(val map: Map<String, String> = mapOf())
932 
933   @Test fun mapOfStringToStandardCodegenWildcards() {
934     mapWildcardsParameterizedTest(
935       MapOfStringToStandardCodegen::class.java,
936       """{"map":{"key":"value"}}""",
937       MapOfStringToStandardCodegen(mapOf("key" to "value"))
938     )
939   }
940 
941   @JsonClass(generateAdapter = true)
942   @JvmSuppressWildcards(suppress = false)
943   data class MapOfStringToStandardCodegen(val map: Map<String, String> = mapOf())
944 
945   @Test fun mapOfStringToEnumReflectionWildcards() {
946     mapWildcardsParameterizedTest(
947       MapOfStringToEnumReflection::class.java,
948       """{"map":{"key":"A"}}""",
949       MapOfStringToEnumReflection(mapOf("key" to KotlinEnum.A))
950     )
951   }
952 
953   @JvmSuppressWildcards(suppress = false)
954   data class MapOfStringToEnumReflection(val map: Map<String, KotlinEnum> = mapOf())
955 
956   @Test fun mapOfStringToEnumCodegenWildcards() {
957     mapWildcardsParameterizedTest(
958       MapOfStringToEnumCodegen::class.java,
959       """{"map":{"key":"A"}}""",
960       MapOfStringToEnumCodegen(mapOf("key" to KotlinEnum.A))
961     )
962   }
963 
964   @JsonClass(generateAdapter = true)
965   @JvmSuppressWildcards(suppress = false)
966   data class MapOfStringToEnumCodegen(val map: Map<String, KotlinEnum> = mapOf())
967 
968   @Test fun mapOfStringToCollectionReflectionWildcards() {
969     mapWildcardsParameterizedTest(
970       MapOfStringToCollectionReflection::class.java,
971       """{"map":{"key":[]}}""",
972       MapOfStringToCollectionReflection(mapOf("key" to listOf()))
973     )
974   }
975 
976   @JvmSuppressWildcards(suppress = false)
977   data class MapOfStringToCollectionReflection(val map: Map<String, List<Int>> = mapOf())
978 
979   @Test fun mapOfStringToCollectionCodegenWildcards() {
980     mapWildcardsParameterizedTest(
981       MapOfStringToCollectionCodegen::class.java,
982       """{"map":{"key":[]}}""",
983       MapOfStringToCollectionCodegen(mapOf("key" to listOf()))
984     )
985   }
986 
987   @JsonClass(generateAdapter = true)
988   @JvmSuppressWildcards(suppress = false)
989   data class MapOfStringToCollectionCodegen(val map: Map<String, List<Int>> = mapOf())
990 
991   @Test fun mapOfStringToMapReflectionWildcards() {
992     mapWildcardsParameterizedTest(
993       MapOfStringToMapReflection::class.java,
994       """{"map":{"key":{}}}""",
995       MapOfStringToMapReflection(mapOf("key" to mapOf()))
996     )
997   }
998 
999   @JvmSuppressWildcards(suppress = false)
1000   data class MapOfStringToMapReflection(val map: Map<String, Map<String, Int>> = mapOf())
1001 
1002   @Test fun mapOfStringToMapCodegenWildcards() {
1003     mapWildcardsParameterizedTest(
1004       MapOfStringToMapCodegen::class.java,
1005       """{"map":{"key":{}}}""",
1006       MapOfStringToMapCodegen(mapOf("key" to mapOf()))
1007     )
1008   }
1009 
1010   @JsonClass(generateAdapter = true)
1011   @JvmSuppressWildcards(suppress = false)
1012   data class MapOfStringToMapCodegen(val map: Map<String, Map<String, Int>> = mapOf())
1013 
1014   @Test fun mapOfStringToArrayReflectionWildcards() {
1015     mapWildcardsParameterizedTest(
1016       MapOfStringToArrayReflection::class.java,
1017       """{"map":{"key":[]}}""",
1018       MapOfStringToArrayReflection(mapOf("key" to arrayOf()))
1019     )
1020   }
1021 
1022   @JvmSuppressWildcards(suppress = false)
1023   data class MapOfStringToArrayReflection(val map: Map<String, Array<Int>> = mapOf())
1024 
1025   @Test fun mapOfStringToArrayCodegenWildcards() {
1026     mapWildcardsParameterizedTest(
1027       MapOfStringToArrayCodegen::class.java,
1028       """{"map":{"key":[]}}""",
1029       MapOfStringToArrayCodegen(mapOf("key" to arrayOf()))
1030     )
1031   }
1032 
1033   @JsonClass(generateAdapter = true)
1034   @JvmSuppressWildcards(suppress = false)
1035   data class MapOfStringToArrayCodegen(val map: Map<String, Array<Int>> = mapOf())
1036 
1037   @Test fun mapOfStringToClassReflectionWildcards() {
1038     mapWildcardsParameterizedTest(
1039       MapOfStringToClassReflection::class.java,
1040       """{"map":{"key":{"a":19,"b":42}}}""",
1041       MapOfStringToClassReflection(mapOf("key" to ConstructorParameters(19, 42)))
1042     )
1043   }
1044 
1045   @JvmSuppressWildcards(suppress = false)
1046   data class MapOfStringToClassReflection(val map: Map<String, ConstructorParameters> = mapOf())
1047 
1048   @Test fun mapOfStringToClassCodegenWildcards() {
1049     mapWildcardsParameterizedTest(
1050       MapOfStringToClassCodegen::class.java,
1051       """{"map":{"key":{"a":19,"b":42}}}""",
1052       MapOfStringToClassCodegen(mapOf("key" to ConstructorParameters(19, 42)))
1053     )
1054   }
1055 
1056   @JsonClass(generateAdapter = true)
1057   @JvmSuppressWildcards(suppress = false)
1058   data class MapOfStringToClassCodegen(val map: Map<String, ConstructorParameters> = mapOf())
1059 
1060   @Test fun sealedClassesAreRejected() {
1061     val moshi = Moshi.Builder()
1062       .add(KotlinJsonAdapterFactory())
1063       .build()
1064 
1065     try {
1066       moshi.adapter<SealedClass>()
1067       fail()
1068     } catch (e: IllegalArgumentException) {
1069       assertThat(e).hasMessageThat().contains("Cannot reflectively serialize sealed class")
1070     }
1071   }
1072 
1073   sealed class SealedClass
1074 
1075   private fun <T> mapWildcardsParameterizedTest(type: Class<T>, json: String, value: T) {
1076     // Ensure the map was created with the expected wildcards of a Kotlin map.
1077     val fieldType = type.getDeclaredField("map").genericType
1078     val fieldTypeArguments = (fieldType as ParameterizedType).actualTypeArguments
1079     assertThat(fieldTypeArguments[0]).isNotInstanceOf(WildcardType::class.java)
1080     assertThat(fieldTypeArguments[1]).isInstanceOf(WildcardType::class.java)
1081 
1082     val moshi = Moshi.Builder().add(KotlinJsonAdapterFactory()).build()
1083     val adapter = moshi.adapter(type)
1084 
1085     Assertions.assertThat(adapter.fromJson(json)).isEqualToComparingFieldByFieldRecursively(value)
1086     assertThat(adapter.toJson(value)).isEqualTo(json)
1087   }
1088 }
1089