1<!--- TEST_NAME PolymorphismTest --> 2 3# Polymorphism 4 5This is the fourth chapter of the [Kotlin Serialization Guide](serialization-guide.md). 6In this chapter we'll see how Kotlin Serialization deals with polymorphic class hierarchies. 7 8**Table of contents** 9 10<!--- TOC --> 11 12* [Closed polymorphism](#closed-polymorphism) 13 * [Static types](#static-types) 14 * [Designing serializable hierarchy](#designing-serializable-hierarchy) 15 * [Sealed classes](#sealed-classes) 16 * [Custom subclass serial name](#custom-subclass-serial-name) 17 * [Concrete properties in a base class](#concrete-properties-in-a-base-class) 18 * [Objects](#objects) 19* [Open polymorphism](#open-polymorphism) 20 * [Registered subclasses](#registered-subclasses) 21 * [Serializing interfaces](#serializing-interfaces) 22 * [Property of an interface type](#property-of-an-interface-type) 23 * [Static parent type lookup for polymorphism](#static-parent-type-lookup-for-polymorphism) 24 * [Explicitly marking polymorphic class properties](#explicitly-marking-polymorphic-class-properties) 25 * [Registering multiple superclasses](#registering-multiple-superclasses) 26 * [Polymorphism and generic classes](#polymorphism-and-generic-classes) 27 * [Merging library serializers modules](#merging-library-serializers-modules) 28 * [Default polymorphic type handler for deserialization](#default-polymorphic-type-handler-for-deserialization) 29 * [Default polymorphic type handler for serialization](#default-polymorphic-type-handler-for-serialization) 30 31<!--- END --> 32 33<!--- INCLUDE .*-poly-.* 34import kotlinx.serialization.* 35import kotlinx.serialization.json.* 36--> 37 38## Closed polymorphism 39 40Let us start with basic introduction to polymorphism. 41 42### Static types 43 44Kotlin Serialization is fully static with respect to types by default. The structure of encoded objects is determined 45by *compile-time* types of objects. Let's examine this aspect in more detail and learn how 46to serialize polymorphic data structures, where the type of data is determined at runtime. 47 48To show the static nature of Kotlin Serialization let us make the following setup. An `open class Project` 49has just the `name` property, while its derived `class OwnedProject` adds an `owner` property. 50In the below example, we serialize `data` variable with a static type of 51`Project` that is initialized with an instance of `OwnedProject` at runtime. 52 53```kotlin 54@Serializable 55open class Project(val name: String) 56 57class OwnedProject(name: String, val owner: String) : Project(name) 58 59fun main() { 60 val data: Project = OwnedProject("kotlinx.coroutines", "kotlin") 61 println(Json.encodeToString(data)) 62} 63``` 64 65> You can get the full code [here](../guide/example/example-poly-01.kt). 66 67Despite the runtime type of `OwnedProject`, only the `Project` class properties are getting serialized. 68 69```text 70{"name":"kotlinx.coroutines"} 71``` 72 73<!--- TEST --> 74 75Let's change the compile-time type of `data` to `OwnedProject`. 76 77```kotlin 78@Serializable 79open class Project(val name: String) 80 81class OwnedProject(name: String, val owner: String) : Project(name) 82 83fun main() { 84 val data = OwnedProject("kotlinx.coroutines", "kotlin") 85 println(Json.encodeToString(data)) 86} 87``` 88 89> You can get the full code [here](../guide/example/example-poly-02.kt). 90 91We get an error, because the `OwnedProject` class is not serializable. 92 93```text 94Exception in thread "main" kotlinx.serialization.SerializationException: Serializer for class 'OwnedProject' is not found. 95Please ensure that class is marked as '@Serializable' and that the serialization compiler plugin is applied. 96``` 97 98<!--- TEST LINES_START --> 99 100### Designing serializable hierarchy 101 102We cannot simply mark `OwnedProject` from the previous example as `@Serializable`. It does not compile, 103running into the [constructor properties requirement](basic-serialization.md#constructor-properties-requirement). 104To make hierarchy of classes serializable, the properties in the parent class have to be marked `abstract`, 105making the `Project` class `abstract`, too. 106 107```kotlin 108@Serializable 109abstract class Project { 110 abstract val name: String 111} 112 113class OwnedProject(override val name: String, val owner: String) : Project() 114 115fun main() { 116 val data: Project = OwnedProject("kotlinx.coroutines", "kotlin") 117 println(Json.encodeToString(data)) 118} 119``` 120 121> You can get the full code [here](../guide/example/example-poly-03.kt). 122 123This is close to the best design for a serializable hierarchy of classes, but running it produces the following error: 124 125```text 126Exception in thread "main" kotlinx.serialization.SerializationException: Serializer for subclass 'OwnedProject' is not found in the polymorphic scope of 'Project'. 127Check if class with serial name 'OwnedProject' exists and serializer is registered in a corresponding SerializersModule. 128To be registered automatically, class 'OwnedProject' has to be '@Serializable', and the base class 'Project' has to be sealed and '@Serializable'. 129``` 130 131<!--- TEST LINES_START --> 132 133### Sealed classes 134 135The most straightforward way to use serialization with a polymorphic hierarchy is to mark the base class `sealed`. 136_All_ subclasses of a sealed class must be explicitly marked as `@Serializable`. 137 138```kotlin 139@Serializable 140sealed class Project { 141 abstract val name: String 142} 143 144@Serializable 145class OwnedProject(override val name: String, val owner: String) : Project() 146 147fun main() { 148 val data: Project = OwnedProject("kotlinx.coroutines", "kotlin") 149 println(Json.encodeToString(data)) // Serializing data of compile-time type Project 150} 151``` 152 153> You can get the full code [here](../guide/example/example-poly-04.kt). 154 155Now we can see a default way to represent polymorphism in JSON. 156A `type` key is added to the resulting JSON object as a _discriminator_. 157 158```text 159{"type":"example.examplePoly04.OwnedProject","name":"kotlinx.coroutines","owner":"kotlin"} 160``` 161 162<!--- TEST --> 163 164Pay attention to the small, but very important detail in the above example that is related to [Static types](#static-types): 165the `val data` property has a compile-time type of `Project`, even though its run-time type is `OwnedProject`. 166When serializing polymorphic class hierarchies you must ensure that the compile-time type of the serialized object 167is a polymorphic one, not a concrete one. 168 169Let us see what happens if the example is slightly changed, so that the compile-time of the object that is being 170serialized is `OwnedProject` (the same as its run-time type). 171 172```kotlin 173@Serializable 174sealed class Project { 175 abstract val name: String 176} 177 178@Serializable 179class OwnedProject(override val name: String, val owner: String) : Project() 180 181fun main() { 182 val data = OwnedProject("kotlinx.coroutines", "kotlin") // data: OwnedProject here 183 println(Json.encodeToString(data)) // Serializing data of compile-time type OwnedProject 184} 185``` 186 187> You can get the full code [here](../guide/example/example-poly-05.kt). 188 189The type of `OwnedProject` is concrete and is not polymorphic, thus the `type` 190discriminator property is not emitted into the resulting JSON. 191 192```text 193{"name":"kotlinx.coroutines","owner":"kotlin"} 194``` 195 196<!--- TEST --> 197 198In general, Kotlin Serialization is designed to work correctly only when the compile-time type used during serialization 199is the same one as the compile-time type used during deserialization. You can always specify the type explicitly 200when calling serialization functions. The previous example can be corrected to use `Project` type for serialization 201by calling `Json.encodeToString<Project>(data)`. 202 203### Custom subclass serial name 204 205A value of the `type` key is a fully qualified class name by default. We can put [SerialName] annotation onto 206the corresponding class to change it. 207 208```kotlin 209@Serializable 210sealed class Project { 211 abstract val name: String 212} 213 214@Serializable 215@SerialName("owned") 216class OwnedProject(override val name: String, val owner: String) : Project() 217 218fun main() { 219 val data: Project = OwnedProject("kotlinx.coroutines", "kotlin") 220 println(Json.encodeToString(data)) 221} 222``` 223 224> You can get the full code [here](../guide/example/example-poly-06.kt). 225 226This way we can have a stable _serial name_ that is not affected by the class's name in the source code. 227 228```text 229{"type":"owned","name":"kotlinx.coroutines","owner":"kotlin"} 230``` 231 232<!--- TEST --> 233 234> In addition to that, JSON can be configured to use a different key name for the class discriminator. 235> You can find an example in the [Class discriminator for polymorphism](json.md#class-discriminator-for-polymorphism) section. 236 237### Concrete properties in a base class 238 239A base class in a sealed hierarchy can have properties with backing fields. 240 241```kotlin 242@Serializable 243sealed class Project { 244 abstract val name: String 245 var status = "open" 246} 247 248@Serializable 249@SerialName("owned") 250class OwnedProject(override val name: String, val owner: String) : Project() 251 252fun main() { 253 val json = Json { encodeDefaults = true } // "status" will be skipped otherwise 254 val data: Project = OwnedProject("kotlinx.coroutines", "kotlin") 255 println(json.encodeToString(data)) 256} 257``` 258 259> You can get the full code [here](../guide/example/example-poly-07.kt). 260 261The properties of the superclass are serialized before the properties of the subclass. 262 263```text 264{"type":"owned","status":"open","name":"kotlinx.coroutines","owner":"kotlin"} 265``` 266 267<!--- TEST --> 268 269### Objects 270 271Sealed hierarchies can have objects as their subclasses and they also need to be marked as `@Serializable`. 272Let's take a different example with a hierarchy of `Response` classes. 273 274```kotlin 275@Serializable 276sealed class Response 277 278@Serializable 279object EmptyResponse : Response() 280 281@Serializable 282class TextResponse(val text: String) : Response() 283``` 284 285Let us serialize a list of different responses. 286 287```kotlin 288fun main() { 289 val list = listOf(EmptyResponse, TextResponse("OK")) 290 println(Json.encodeToString(list)) 291} 292``` 293 294> You can get the full code [here](../guide/example/example-poly-08.kt). 295 296An object serializes as an empty class, also using its fully-qualified class name as type by default: 297 298```text 299[{"type":"example.examplePoly08.EmptyResponse"},{"type":"example.examplePoly08.TextResponse","text":"OK"}] 300``` 301 302<!--- TEST --> 303 304> Even if object has properties, they are not serialized. 305 306## Open polymorphism 307 308Serialization can work with arbitrary `open` classes or `abstract` classes. 309However, since this kind of polymorphism is open, there is a possibility that subclasses are defined anywhere in the 310source code, even in other modules, the list of subclasses that are serialized cannot be determined at compile-time and 311must be explicitly registered at runtime. 312 313### Registered subclasses 314 315Let us start with the code from the [Designing serializable hierarchy](#designing-serializable-hierarchy) section. 316To make it work with serialization without making it `sealed`, we have to define a [SerializersModule] using the 317[SerializersModule {}][SerializersModule()] builder function. In the module the base class is specified 318in the [polymorphic][_polymorphic] builder and each subclass is registered with the [subclass] function. Now, 319a custom JSON configuration can be instantiated with this module and used for serialization. 320 321> Details on custom JSON configurations can be found in 322> the [JSON configuration](json.md#json-configuration) section. 323 324<!--- INCLUDE 325import kotlinx.serialization.modules.* 326--> 327 328```kotlin 329val module = SerializersModule { 330 polymorphic(Project::class) { 331 subclass(OwnedProject::class) 332 } 333} 334 335val format = Json { serializersModule = module } 336 337@Serializable 338abstract class Project { 339 abstract val name: String 340} 341 342@Serializable 343@SerialName("owned") 344class OwnedProject(override val name: String, val owner: String) : Project() 345 346fun main() { 347 val data: Project = OwnedProject("kotlinx.coroutines", "kotlin") 348 println(format.encodeToString(data)) 349} 350``` 351 352> You can get the full code [here](../guide/example/example-poly-09.kt). 353 354This additional configuration makes our code work just as it worked with a sealed class in 355the [Sealed classes](#sealed-classes) section, but here subclasses can be spread arbitrarily throughout the code. 356 357```text 358{"type":"owned","name":"kotlinx.coroutines","owner":"kotlin"} 359``` 360 361<!--- TEST --> 362>Please note that this example works only on JVM because of `serializer` function restrictions. 363>For JS and Native, explicit serializer should be used: `format.encodeToString(PolymorphicSerializer(Project::class), data)` 364>You can keep track of this issue [here](https://github.com/Kotlin/kotlinx.serialization/issues/1077). 365 366### Serializing interfaces 367 368We can update the previous example and turn `Project` superclass into an interface. However, we cannot 369mark an interface itself as `@Serializable`. No problem. Interfaces cannot have instances by themselves. 370Interfaces can only be represented by instances of their derived classes. Interfaces are used in the Kotlin language to enable polymorphism, 371so all interfaces are considered to be implicitly serializable with the [PolymorphicSerializer] 372strategy. We just need to mark their implementing classes as `@Serializable` and register them. 373 374<!--- INCLUDE 375import kotlinx.serialization.modules.* 376 377val module = SerializersModule { 378 polymorphic(Project::class) { 379 subclass(OwnedProject::class) 380 } 381} 382 383val format = Json { serializersModule = module } 384--> 385 386```kotlin 387interface Project { 388 val name: String 389} 390 391@Serializable 392@SerialName("owned") 393class OwnedProject(override val name: String, val owner: String) : Project 394``` 395 396Now if we declare `data` with the type of `Project` we can simply call `format.encodeToString` as before. 397 398```kotlin 399fun main() { 400 val data: Project = OwnedProject("kotlinx.coroutines", "kotlin") 401 println(format.encodeToString(data)) 402} 403``` 404 405> You can get the full code [here](../guide/example/example-poly-10.kt). 406 407```text 408{"type":"owned","name":"kotlinx.coroutines","owner":"kotlin"} 409``` 410 411> Note: On Kotlin/Native, you should use `format.encodeToString(PolymorphicSerializer(Project::class), data))` instead due to limited reflection capabilities. 412 413<!--- TEST LINES_START --> 414 415### Property of an interface type 416 417Continuing the previous example, let us see what happens if we use `Project` interface as a property in some 418other serializable class. Interfaces are implicitly polymorphic, so we can just declare a property of an interface type. 419 420<!--- INCLUDE 421import kotlinx.serialization.modules.* 422 423val module = SerializersModule { 424 polymorphic(Project::class) { 425 subclass(OwnedProject::class) 426 } 427} 428 429val format = Json { serializersModule = module } 430 431interface Project { 432 val name: String 433} 434 435@Serializable 436@SerialName("owned") 437class OwnedProject(override val name: String, val owner: String) : Project 438--> 439 440```kotlin 441@Serializable 442class Data(val project: Project) // Project is an interface 443 444fun main() { 445 val data = Data(OwnedProject("kotlinx.coroutines", "kotlin")) 446 println(format.encodeToString(data)) 447} 448``` 449 450> You can get the full code [here](../guide/example/example-poly-11.kt). 451 452As long as we've registered the actual subtype of the interface that is being serialized in 453the [SerializersModule] of our `format`, we get it working at runtime. 454 455```text 456{"project":{"type":"owned","name":"kotlinx.coroutines","owner":"kotlin"}} 457``` 458 459<!--- TEST --> 460 461### Static parent type lookup for polymorphism 462 463During serialization of a polymorphic class the root type of the polymorphic hierarchy (`Project` in our example) 464is determined statically. Let us take the example with the serializable `abstract class Project`, 465but change the `main` function to declare `data` as having a type of `Any`: 466 467<!--- INCLUDE 468import kotlinx.serialization.modules.* 469 470val module = SerializersModule { 471 polymorphic(Project::class) { 472 subclass(OwnedProject::class) 473 } 474} 475 476val format = Json { serializersModule = module } 477 478@Serializable 479abstract class Project { 480 abstract val name: String 481} 482 483@Serializable 484@SerialName("owned") 485class OwnedProject(override val name: String, val owner: String) : Project() 486--> 487 488```kotlin 489fun main() { 490 val data: Any = OwnedProject("kotlinx.coroutines", "kotlin") 491 println(format.encodeToString(data)) 492} 493``` 494 495> You can get the full code [here](../guide/example/example-poly-12.kt). 496 497We get the exception. 498 499```text 500Exception in thread "main" kotlinx.serialization.SerializationException: Serializer for class 'Any' is not found. 501Please ensure that class is marked as '@Serializable' and that the serialization compiler plugin is applied. 502``` 503 504<!--- TEST LINES_START --> 505 506We have to register classes for polymorphic serialization with respect for the corresponding static type we 507use in the source code. First of all, we change our module to register a subclass of `Any`: 508 509<!--- INCLUDE 510import kotlinx.serialization.modules.* 511--> 512 513```kotlin 514val module = SerializersModule { 515 polymorphic(Any::class) { 516 subclass(OwnedProject::class) 517 } 518} 519``` 520 521<!--- INCLUDE 522val format = Json { serializersModule = module } 523 524@Serializable 525abstract class Project { 526 abstract val name: String 527} 528 529@Serializable 530@SerialName("owned") 531class OwnedProject(override val name: String, val owner: String) : Project() 532--> 533 534Then we can try to serialize the variable of type `Any`: 535 536```kotlin 537fun main() { 538 val data: Any = OwnedProject("kotlinx.coroutines", "kotlin") 539 println(format.encodeToString(data)) 540} 541``` 542 543> You can get the full code [here](../guide/example/example-poly-13.kt). 544 545However, `Any` is a class and it is not serializable: 546 547```text 548Exception in thread "main" kotlinx.serialization.SerializationException: Serializer for class 'Any' is not found. 549Please ensure that class is marked as '@Serializable' and that the serialization compiler plugin is applied. 550``` 551 552<!--- TEST LINES_START --> 553 554We must to explicitly pass an instance of [PolymorphicSerializer] for the base class `Any` as the 555first parameter to the [encodeToString][Json.encodeToString] function. 556 557<!--- INCLUDE 558import kotlinx.serialization.modules.* 559 560val module = SerializersModule { 561 polymorphic(Any::class) { 562 subclass(OwnedProject::class) 563 } 564} 565 566val format = Json { serializersModule = module } 567 568@Serializable 569abstract class Project { 570 abstract val name: String 571} 572 573@Serializable 574@SerialName("owned") 575class OwnedProject(override val name: String, val owner: String) : Project() 576--> 577 578```kotlin 579fun main() { 580 val data: Any = OwnedProject("kotlinx.coroutines", "kotlin") 581 println(format.encodeToString(PolymorphicSerializer(Any::class), data)) 582} 583``` 584 585> You can get the full code [here](../guide/example/example-poly-14.kt). 586 587With the explicit serializer it works as before. 588 589```text 590{"type":"owned","name":"kotlinx.coroutines","owner":"kotlin"} 591``` 592 593<!--- TEST --> 594 595### Explicitly marking polymorphic class properties 596 597The property of an interface type is implicitly considered polymorphic, since interfaces are all about runtime polymorphism. 598However, Kotlin Serialization does not compile a serializable class with a property of a non-serializable class type. 599If we have a property of `Any` class or other non-serializable class, then we must explicitly provide its serialization 600strategy via the [`@Serializable`][Serializable] annotation as we saw in 601the [Specifying serializer on a property](serializers.md#specifying-serializer-on-a-property) section. 602To specify a polymorphic serialization strategy of a property, the special-purpose [`@Polymorphic`][Polymorphic] 603annotation is used. 604 605<!--- INCLUDE 606import kotlinx.serialization.modules.* 607 608val module = SerializersModule { 609 polymorphic(Any::class) { 610 subclass(OwnedProject::class) 611 } 612} 613 614val format = Json { serializersModule = module } 615 616interface Project { 617 val name: String 618} 619 620@Serializable 621@SerialName("owned") 622class OwnedProject(override val name: String, val owner: String) : Project 623--> 624 625```kotlin 626@Serializable 627class Data( 628 @Polymorphic // the code does not compile without it 629 val project: Any 630) 631 632fun main() { 633 val data = Data(OwnedProject("kotlinx.coroutines", "kotlin")) 634 println(format.encodeToString(data)) 635} 636``` 637 638> You can get the full code [here](../guide/example/example-poly-15.kt). 639 640<!--- TEST 641{"project":{"type":"owned","name":"kotlinx.coroutines","owner":"kotlin"}} 642--> 643 644### Registering multiple superclasses 645 646When the same class gets serialized as a value of properties with different compile-time type from the list of 647its superclasses, we must register it in the [SerializersModule] for each of its superclasses separately. 648It is convenient to extract registration of all the subclasses into a separate function and 649use it for each superclass. You can use the following template to write it. 650 651<!--- INCLUDE 652import kotlinx.serialization.modules.* 653import kotlin.reflect.KClass 654--> 655 656```kotlin 657val module = SerializersModule { 658 fun PolymorphicModuleBuilder<Project>.registerProjectSubclasses() { 659 subclass(OwnedProject::class) 660 } 661 polymorphic(Any::class) { registerProjectSubclasses() } 662 polymorphic(Project::class) { registerProjectSubclasses() } 663} 664``` 665 666<!--- INCLUDE 667 668val format = Json { serializersModule = module } 669 670interface Project { 671 val name: String 672} 673 674@Serializable 675@SerialName("owned") 676class OwnedProject(override val name: String, val owner: String) : Project 677 678@Serializable 679class Data( 680 val project: Project, 681 @Polymorphic val any: Any 682) 683 684fun main() { 685 val project = OwnedProject("kotlinx.coroutines", "kotlin") 686 val data = Data(project, project) 687 println(format.encodeToString(data)) 688} 689--> 690 691> You can get the full code [here](../guide/example/example-poly-16.kt). 692 693<!--- TEST 694{"project":{"type":"owned","name":"kotlinx.coroutines","owner":"kotlin"},"any":{"type":"owned","name":"kotlinx.coroutines","owner":"kotlin"}} 695--> 696 697### Polymorphism and generic classes 698 699Generic subtypes for a serializable class require a special handling. Consider the following hierarchy. 700 701<!--- INCLUDE 702import kotlinx.serialization.modules.* 703--> 704 705```kotlin 706@Serializable 707abstract class Response<out T> 708 709@Serializable 710@SerialName("OkResponse") 711data class OkResponse<out T>(val data: T) : Response<T>() 712``` 713 714Kotlin Serialization does not have a builtin strategy to represent the actually provided argument type for the 715type parameter `T` when serializing a property of the polymorphic type `OkResponse<T>`. We have to provide this 716strategy explicitly when defining the serializers module for the `Response`. In the below example we 717use `OkResponse.serializer(...)` to retrieve 718the [Plugin-generated generic serializer](serializers.md#plugin-generated-generic-serializer) of 719the `OkResponse` class and instantiate it with the [PolymorphicSerializer] instance with 720`Any` class as its base. This way, we can serialize an instance of `OkResponse` with any `data` property that 721was polymorphically registered as a subtype of `Any`. 722 723```kotlin 724val responseModule = SerializersModule { 725 polymorphic(Response::class) { 726 subclass(OkResponse.serializer(PolymorphicSerializer(Any::class))) 727 } 728} 729``` 730 731### Merging library serializers modules 732 733When the application grows in size and splits into source code modules, 734it may become inconvenient to store all class hierarchies in one serializers module. 735Let us add a library with the `Project` hierarchy to the code from the previous section. 736 737```kotlin 738val projectModule = SerializersModule { 739 fun PolymorphicModuleBuilder<Project>.registerProjectSubclasses() { 740 subclass(OwnedProject::class) 741 } 742 polymorphic(Any::class) { registerProjectSubclasses() } 743 polymorphic(Project::class) { registerProjectSubclasses() } 744} 745``` 746 747<!--- INCLUDE 748 749@Serializable 750abstract class Project { 751 abstract val name: String 752} 753 754@Serializable 755@SerialName("OwnedProject") 756data class OwnedProject(override val name: String, val owner: String) : Project() 757--> 758 759We can compose those two modules together using the [plus] operator to merge them, 760so that we can use them both in the same [Json] format instance. 761 762> You can also use the [include][SerializersModuleBuilder.include] function 763> in the [SerializersModule {}][SerializersModule()] DSL. 764 765```kotlin 766val format = Json { serializersModule = projectModule + responseModule } 767```` 768 769Now classes from both hierarchies can be serialized together and deserialized together. 770 771```kotlin 772fun main() { 773 // both Response and Project are abstract and their concrete subtypes are being serialized 774 val data: Response<Project> = OkResponse(OwnedProject("kotlinx.serialization", "kotlin")) 775 val string = format.encodeToString(data) 776 println(string) 777 println(format.decodeFromString<Response<Project>>(string)) 778} 779 780``` 781 782> You can get the full code [here](../guide/example/example-poly-17.kt). 783 784The JSON that is being produced is deeply polymorphic. 785 786```text 787{"type":"OkResponse","data":{"type":"OwnedProject","name":"kotlinx.serialization","owner":"kotlin"}} 788OkResponse(data=OwnedProject(name=kotlinx.serialization, owner=kotlin)) 789``` 790 791<!--- TEST --> 792 793If you're writing a library or shared module with an abstract class and some implementations of it, 794you can expose your own serializers module for your clients to use so that a client can combine your 795module with their modules. 796 797### Default polymorphic type handler for deserialization 798 799What happens when we deserialize a subclass that was not registered? 800 801<!--- INCLUDE 802import kotlinx.serialization.modules.* 803 804@Serializable 805abstract class Project { 806 abstract val name: String 807} 808 809@Serializable 810@SerialName("OwnedProject") 811data class OwnedProject(override val name: String, val owner: String) : Project() 812 813val module = SerializersModule { 814 polymorphic(Project::class) { 815 subclass(OwnedProject::class) 816 } 817} 818 819val format = Json { serializersModule = module } 820--> 821 822```kotlin 823fun main() { 824 println(format.decodeFromString<Project>(""" 825 {"type":"unknown","name":"example"} 826 """)) 827} 828``` 829 830> You can get the full code [here](../guide/example/example-poly-18.kt). 831 832We get the following exception. 833 834```text 835Exception in thread "main" kotlinx.serialization.json.internal.JsonDecodingException: Unexpected JSON token at offset 0: Serializer for subclass 'unknown' is not found in the polymorphic scope of 'Project' at path: $ 836Check if class with serial name 'unknown' exists and serializer is registered in a corresponding SerializersModule. 837``` 838 839<!--- TEST LINES_START --> 840 841When reading a flexible input we might want to provide some default behavior in this case. For example, 842we can have a `BasicProject` subtype to represent all kinds of unknown `Project` subtypes. 843 844<!--- INCLUDE 845import kotlinx.serialization.modules.* 846--> 847 848```kotlin 849@Serializable 850abstract class Project { 851 abstract val name: String 852} 853 854@Serializable 855data class BasicProject(override val name: String, val type: String): Project() 856 857@Serializable 858@SerialName("OwnedProject") 859data class OwnedProject(override val name: String, val owner: String) : Project() 860``` 861 862We register a default deserializer handler using the [`defaultDeserializer`][PolymorphicModuleBuilder.defaultDeserializer] function in 863the [`polymorphic { ... }`][PolymorphicModuleBuilder] DSL that defines a strategy which maps the `type` string from the input 864to the [deserialization strategy][DeserializationStrategy]. In the below example we don't use the type, 865but always return the [Plugin-generated serializer](serializers.md#plugin-generated-serializer) 866of the `BasicProject` class. 867 868```kotlin 869val module = SerializersModule { 870 polymorphic(Project::class) { 871 subclass(OwnedProject::class) 872 defaultDeserializer { BasicProject.serializer() } 873 } 874} 875``` 876 877Using this module we can now deserialize both instances of the registered `OwnedProject` and 878any unregistered one. 879 880```kotlin 881val format = Json { serializersModule = module } 882 883fun main() { 884 println(format.decodeFromString<List<Project>>(""" 885 [ 886 {"type":"unknown","name":"example"}, 887 {"type":"OwnedProject","name":"kotlinx.serialization","owner":"kotlin"} 888 ] 889 """)) 890} 891``` 892 893> You can get the full code [here](../guide/example/example-poly-19.kt). 894 895Notice, how `BasicProject` had also captured the specified type key in its `type` property. 896 897```text 898[BasicProject(name=example, type=unknown), OwnedProject(name=kotlinx.serialization, owner=kotlin)] 899``` 900 901<!--- TEST --> 902 903We used a plugin-generated serializer as a default serializer, implying that 904the structure of the "unknown" data is known in advance. In a real-world API it's rarely the case. 905For that purpose a custom, less-structured serializer is needed. You will see the example of such serializer in the future section 906on [Maintaining custom JSON attributes](json.md#maintaining-custom-json-attributes). 907 908### Default polymorphic type handler for serialization 909 910Sometimes you need to dynamically choose which serializer to use for a polymorphic type based on the instance, for example if you 911don't have access to the full type hierarchy, or if it changes a lot. For this situation, you can register a default serializer. 912 913<!--- INCLUDE 914import kotlinx.serialization.descriptors.* 915import kotlinx.serialization.encoding.* 916import kotlinx.serialization.modules.* 917--> 918 919```kotlin 920interface Animal { 921} 922 923interface Cat : Animal { 924 val catType: String 925} 926 927interface Dog : Animal { 928 val dogType: String 929} 930 931private class CatImpl : Cat { 932 override val catType: String = "Tabby" 933} 934 935private class DogImpl : Dog { 936 override val dogType: String = "Husky" 937} 938 939object AnimalProvider { 940 fun createCat(): Cat = CatImpl() 941 fun createDog(): Dog = DogImpl() 942} 943``` 944 945We register a default serializer handler using the [`polymorphicDefaultSerializer`][SerializersModuleBuilder.polymorphicDefaultSerializer] function in 946the [`SerializersModule { ... }`][SerializersModuleBuilder] DSL that defines a strategy which takes an instance of the base class and 947provides a [serialization strategy][SerializationStrategy]. In the below example we use a `when` block to check the type of the 948instance, without ever having to refer to the private implementation classes. 949 950```kotlin 951val module = SerializersModule { 952 polymorphicDefaultSerializer(Animal::class) { instance -> 953 @Suppress("UNCHECKED_CAST") 954 when (instance) { 955 is Cat -> CatSerializer as SerializationStrategy<Animal> 956 is Dog -> DogSerializer as SerializationStrategy<Animal> 957 else -> null 958 } 959 } 960} 961 962object CatSerializer : SerializationStrategy<Cat> { 963 override val descriptor = buildClassSerialDescriptor("Cat") { 964 element<String>("catType") 965 } 966 967 override fun serialize(encoder: Encoder, value: Cat) { 968 encoder.encodeStructure(descriptor) { 969 encodeStringElement(descriptor, 0, value.catType) 970 } 971 } 972} 973 974object DogSerializer : SerializationStrategy<Dog> { 975 override val descriptor = buildClassSerialDescriptor("Dog") { 976 element<String>("dogType") 977 } 978 979 override fun serialize(encoder: Encoder, value: Dog) { 980 encoder.encodeStructure(descriptor) { 981 encodeStringElement(descriptor, 0, value.dogType) 982 } 983 } 984} 985``` 986 987Using this module we can now serialize instances of `Cat` and `Dog`. 988 989```kotlin 990val format = Json { serializersModule = module } 991 992fun main() { 993 println(format.encodeToString<Animal>(AnimalProvider.createCat())) 994} 995``` 996 997> You can get the full code [here](../guide/example/example-poly-20.kt) 998 999```text 1000{"type":"Cat","catType":"Tabby"} 1001``` 1002 1003 1004<!--- TEST --> 1005 1006--- 1007 1008The next chapter covers [JSON features](json.md). 1009 1010<!--- MODULE /kotlinx-serialization-core --> 1011<!--- INDEX kotlinx-serialization-core/kotlinx.serialization --> 1012 1013[SerialName]: https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-core/kotlinx.serialization/-serial-name/index.html 1014[PolymorphicSerializer]: https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-core/kotlinx.serialization/-polymorphic-serializer/index.html 1015[Serializable]: https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-core/kotlinx.serialization/-serializable/index.html 1016[Polymorphic]: https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-core/kotlinx.serialization/-polymorphic/index.html 1017[DeserializationStrategy]: https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-core/kotlinx.serialization/-deserialization-strategy/index.html 1018[SerializationStrategy]: https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-core/kotlinx.serialization/-serialization-strategy/index.html 1019 1020<!--- INDEX kotlinx-serialization-core/kotlinx.serialization.modules --> 1021 1022[SerializersModule]: https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-core/kotlinx.serialization.modules/-serializers-module/index.html 1023[SerializersModule()]: https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-core/kotlinx.serialization.modules/-serializers-module.html 1024[_polymorphic]: https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-core/kotlinx.serialization.modules/polymorphic.html 1025[subclass]: https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-core/kotlinx.serialization.modules/subclass.html 1026[plus]: https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-core/kotlinx.serialization.modules/plus.html 1027[SerializersModuleBuilder.include]: https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-core/kotlinx.serialization.modules/-serializers-module-builder/include.html 1028[PolymorphicModuleBuilder.defaultDeserializer]: https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-core/kotlinx.serialization.modules/-polymorphic-module-builder/default-deserializer.html 1029[PolymorphicModuleBuilder]: https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-core/kotlinx.serialization.modules/-polymorphic-module-builder/index.html 1030[SerializersModuleBuilder.polymorphicDefaultSerializer]: https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-core/kotlinx.serialization.modules/-serializers-module-builder/polymorphic-default-serializer.html 1031[SerializersModuleBuilder]: https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-core/kotlinx.serialization.modules/-serializers-module-builder/index.html 1032 1033<!--- MODULE /kotlinx-serialization-json --> 1034<!--- INDEX kotlinx-serialization-json/kotlinx.serialization.json --> 1035 1036[Json.encodeToString]: https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-json/kotlinx.serialization.json/-json/encode-to-string.html 1037[Json]: https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-json/kotlinx.serialization.json/-json/index.html 1038 1039<!--- END --> 1040 1041