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