Skip to content

Commit 20d410e

Browse files
committed
Create an implementation that allows automatically registering the sealed children of a class for polymorphic serialization of a shared (non-sealed) type.
1 parent ffa4c3b commit 20d410e

File tree

4 files changed

+98
-1
lines changed

4 files changed

+98
-1
lines changed

core/commonMain/src/kotlinx/serialization/SealedSerializer.kt

+1-1
Original file line numberDiff line numberDiff line change
@@ -115,7 +115,7 @@ public class SealedClassSerializer<T : Any>(
115115
}
116116
}
117117

118-
private val class2Serializer: Map<KClass<out T>, KSerializer<out T>>
118+
internal val class2Serializer: Map<KClass<out T>, KSerializer<out T>>
119119
private val serialName2Serializer: Map<String, KSerializer<out T>>
120120

121121
init {

core/commonMain/src/kotlinx/serialization/modules/PolymorphicModuleBuilder.kt

+27
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,21 @@ public class PolymorphicModuleBuilder<in Base : Any> @PublishedApi internal cons
2323
private var defaultSerializerProvider: ((Base) -> SerializationStrategy<Base>?)? = null
2424
private var defaultDeserializerProvider: ((String?) -> DeserializationStrategy<Base>?)? = null
2525

26+
/**
27+
* Registers the subclasses of the given class as subclasses of the outer class. Currently this requires `baseClass`
28+
* to be sealed.
29+
*/
30+
public fun <T: Base> subclassesOf(baseClass: KClass<T>, serializer: KSerializer<T>) {
31+
require(serializer is SealedClassSerializer) {
32+
"subClassesOf only supports automatic adding of subclasses of sealed types."
33+
}
34+
for ((subsubclass, subserializer) in serializer.class2Serializer.entries) {
35+
@Suppress("UNCHECKED_CAST")
36+
// We don't know the type here, but it matches if correct in the sealed serializer.
37+
subclass(subsubclass as KClass<T>, subserializer as KSerializer<T>)
38+
}
39+
}
40+
2641
/**
2742
* Registers a [subclass] [serializer] in the resulting module under the [base class][Base].
2843
*/
@@ -116,3 +131,15 @@ public inline fun <Base : Any, reified T : Base> PolymorphicModuleBuilder<Base>.
116131
*/
117132
public inline fun <Base : Any, reified T : Base> PolymorphicModuleBuilder<Base>.subclass(clazz: KClass<T>): Unit =
118133
subclass(clazz, serializer())
134+
135+
/**
136+
* Registers the child serializers for the sealed [subclass] [serializer] in the resulting module under the [base class][Base].
137+
*/
138+
public inline fun <Base : Any, reified T : Base> PolymorphicModuleBuilder<Base>.subclassesOf(serializer: KSerializer<T>): Unit =
139+
subclassesOf(T::class, serializer)
140+
141+
/**
142+
* Registers the child serializers for the sealed class [T] in the resulting module under the [base class][Base].
143+
*/
144+
public inline fun <Base : Any, reified T : Base> PolymorphicModuleBuilder<Base>.subclassesOf(clazz: KClass<T>): Unit =
145+
subclassesOf(clazz, serializer())

docs/polymorphism.md

+5
Original file line numberDiff line numberDiff line change
@@ -410,6 +410,11 @@ fun main() {
410410

411411
> Note: On Kotlin/Native, you should use `format.encodeToString(PolymorphicSerializer(Project::class), data))` instead due to limited reflection capabilities.
412412
413+
### Registering sealed children as subclasses
414+
A sealed parent interface or class can be used to directly register all its children using subclassesOf. This will
415+
expose all children that would be available when serializing the parent directly, but now as sealed. Please note that
416+
this is will remain open serialization, and the sealed parent serializer will not be used in serialization.
417+
413418
<!--- TEST LINES_START -->
414419

415420
### Property of an interface type
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
/*
2+
* Copyright 2017-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
3+
*/
4+
5+
package kotlinx.serialization.features
6+
7+
import kotlinx.serialization.*
8+
import kotlinx.serialization.json.Json
9+
import kotlinx.serialization.modules.*
10+
import kotlinx.serialization.test.assertStringFormAndRestored
11+
import kotlin.test.Test
12+
13+
class PolymorphicSealedChildTest {
14+
15+
@Serializable
16+
data class FooHolder(
17+
val someMetadata: Int,
18+
val payload: List<@Polymorphic FooBase>
19+
)
20+
21+
@Serializable
22+
abstract class FooBase
23+
24+
@Serializable
25+
@SerialName("Foo")
26+
sealed class Foo: FooBase() {
27+
@Serializable
28+
@SerialName("Bar")
29+
data class Bar(val bar: Int) : Foo()
30+
@Serializable
31+
@SerialName("Baz")
32+
data class Baz(val baz: String) : Foo()
33+
}
34+
35+
val sealedModule = SerializersModule {
36+
polymorphic(FooBase::class) {
37+
subclassesOf(Foo.serializer())
38+
}
39+
}
40+
41+
val json = Json { serializersModule = sealedModule }
42+
43+
@Test
44+
fun testSaveSealedClassesList() {
45+
assertStringFormAndRestored(
46+
"""{"someMetadata":42,"payload":[
47+
|{"type":"Bar","bar":1},
48+
|{"type":"Baz","baz":"2"}]}""".trimMargin().replace("\n", ""),
49+
FooHolder(42, listOf(Foo.Bar(1), Foo.Baz("2"))),
50+
FooHolder.serializer(),
51+
json,
52+
printResult = true
53+
)
54+
}
55+
56+
@Test
57+
fun testCanSerializeSealedClassPolymorphicallyOnTopLevel() {
58+
assertStringFormAndRestored(
59+
"""{"type":"Bar","bar":1}""",
60+
Foo.Bar(1),
61+
PolymorphicSerializer(FooBase::class),
62+
json
63+
)
64+
}
65+
}

0 commit comments

Comments
 (0)