Skip to content

Commit 8c8ddde

Browse files
committed
Address pull request comments:
- Rename subClassesOf to subClassesOfSealed - also update the function documentation. - Add a check for open polymorphic children (rather than leaving this to failure on format initialisation/validation). - Add a test that the open children will throw an exception - Fix/enhance the documentation extension - Update the apiDump
1 parent 77d8439 commit 8c8ddde

File tree

5 files changed

+95
-15
lines changed

5 files changed

+95
-15
lines changed

core/api/kotlinx-serialization-core.api

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1334,6 +1334,7 @@ public final class kotlinx/serialization/modules/PolymorphicModuleBuilder {
13341334
public final fun default (Lkotlin/jvm/functions/Function1;)V
13351335
public final fun defaultDeserializer (Lkotlin/jvm/functions/Function1;)V
13361336
public final fun subclass (Lkotlin/reflect/KClass;Lkotlinx/serialization/KSerializer;)V
1337+
public final fun subclassesOfSealed (Lkotlinx/serialization/KSerializer;)V
13371338
}
13381339

13391340
public abstract class kotlinx/serialization/modules/SerializersModule {

core/api/kotlinx-serialization-core.klib.api

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -527,9 +527,11 @@ final class <#A: in kotlin/Any> kotlinx.serialization.modules/PolymorphicModuleB
527527
constructor <init>(kotlin.reflect/KClass<#A>, kotlinx.serialization/KSerializer<#A>? = ...) // kotlinx.serialization.modules/PolymorphicModuleBuilder.<init>|<init>(kotlin.reflect.KClass<1:0>;kotlinx.serialization.KSerializer<1:0>?){}[0]
528528

529529
final fun <#A1: #A> subclass(kotlin.reflect/KClass<#A1>, kotlinx.serialization/KSerializer<#A1>) // kotlinx.serialization.modules/PolymorphicModuleBuilder.subclass|subclass(kotlin.reflect.KClass<0:0>;kotlinx.serialization.KSerializer<0:0>){0§<1:0>}[0]
530+
final fun <#A1: #A> subclassesOfSealed(kotlinx.serialization/KSerializer<#A1>) // kotlinx.serialization.modules/PolymorphicModuleBuilder.subclassesOfSealed|subclassesOfSealed(kotlinx.serialization.KSerializer<0:0>){0§<1:0>}[0]
530531
final fun buildTo(kotlinx.serialization.modules/SerializersModuleBuilder) // kotlinx.serialization.modules/PolymorphicModuleBuilder.buildTo|buildTo(kotlinx.serialization.modules.SerializersModuleBuilder){}[0]
531532
final fun default(kotlin/Function1<kotlin/String?, kotlinx.serialization/DeserializationStrategy<#A>?>) // kotlinx.serialization.modules/PolymorphicModuleBuilder.default|default(kotlin.Function1<kotlin.String?,kotlinx.serialization.DeserializationStrategy<1:0>?>){}[0]
532533
final fun defaultDeserializer(kotlin/Function1<kotlin/String?, kotlinx.serialization/DeserializationStrategy<#A>?>) // kotlinx.serialization.modules/PolymorphicModuleBuilder.defaultDeserializer|defaultDeserializer(kotlin.Function1<kotlin.String?,kotlinx.serialization.DeserializationStrategy<1:0>?>){}[0]
534+
final inline fun <#A1: reified #A> subclassesOfSealed() // kotlinx.serialization.modules/PolymorphicModuleBuilder.subclassesOfSealed|subclassesOfSealed(){0§<1:0>}[0]
533535
}
534536

535537
final class <#A: kotlin/Any, #B: #A?> kotlinx.serialization.internal/ReferenceArraySerializer : kotlinx.serialization.internal/CollectionLikeSerializer<#B, kotlin/Array<#B>, kotlin.collections/ArrayList<#B>> { // kotlinx.serialization.internal/ReferenceArraySerializer|null[0]
@@ -1168,6 +1170,7 @@ final inline fun (kotlinx.serialization.encoding/Encoder).kotlinx.serialization.
11681170
final inline fun (kotlinx.serialization.encoding/Encoder).kotlinx.serialization.encoding/encodeStructure(kotlinx.serialization.descriptors/SerialDescriptor, crossinline kotlin/Function1<kotlinx.serialization.encoding/CompositeEncoder, kotlin/Unit>) // kotlinx.serialization.encoding/encodeStructure|encodeStructure@kotlinx.serialization.encoding.Encoder(kotlinx.serialization.descriptors.SerialDescriptor;kotlin.Function1<kotlinx.serialization.encoding.CompositeEncoder,kotlin.Unit>){}[0]
11691171
final inline fun <#A: kotlin/Any, #B: reified #A> (kotlinx.serialization.modules/PolymorphicModuleBuilder<#A>).kotlinx.serialization.modules/subclass(kotlin.reflect/KClass<#B>) // kotlinx.serialization.modules/subclass|subclass@kotlinx.serialization.modules.PolymorphicModuleBuilder<0:0>(kotlin.reflect.KClass<0:1>){0§<kotlin.Any>;1§<0:0>}[0]
11701172
final inline fun <#A: kotlin/Any, #B: reified #A> (kotlinx.serialization.modules/PolymorphicModuleBuilder<#A>).kotlinx.serialization.modules/subclass(kotlinx.serialization/KSerializer<#B>) // kotlinx.serialization.modules/subclass|subclass@kotlinx.serialization.modules.PolymorphicModuleBuilder<0:0>(kotlinx.serialization.KSerializer<0:1>){0§<kotlin.Any>;1§<0:0>}[0]
1173+
final inline fun <#A: kotlin/Any, #B: reified #A> (kotlinx.serialization.modules/PolymorphicModuleBuilder<#A>).kotlinx.serialization.modules/subclassesOfSealed(kotlin.reflect/KClass<#B>) // kotlinx.serialization.modules/subclassesOfSealed|subclassesOfSealed@kotlinx.serialization.modules.PolymorphicModuleBuilder<0:0>(kotlin.reflect.KClass<0:1>){0§<kotlin.Any>;1§<0:0>}[0]
11711174
final inline fun <#A: kotlin/Any> (kotlinx.serialization.modules/SerializersModuleBuilder).kotlinx.serialization.modules/polymorphic(kotlin.reflect/KClass<#A>, kotlinx.serialization/KSerializer<#A>? = ..., kotlin/Function1<kotlinx.serialization.modules/PolymorphicModuleBuilder<#A>, kotlin/Unit> = ...) // kotlinx.serialization.modules/polymorphic|polymorphic@kotlinx.serialization.modules.SerializersModuleBuilder(kotlin.reflect.KClass<0:0>;kotlinx.serialization.KSerializer<0:0>?;kotlin.Function1<kotlinx.serialization.modules.PolymorphicModuleBuilder<0:0>,kotlin.Unit>){0§<kotlin.Any>}[0]
11721175
final inline fun <#A: kotlin/Any?> (kotlinx.serialization.encoding/Decoder).kotlinx.serialization.encoding/decodeStructure(kotlinx.serialization.descriptors/SerialDescriptor, crossinline kotlin/Function1<kotlinx.serialization.encoding/CompositeDecoder, #A>): #A // kotlinx.serialization.encoding/decodeStructure|decodeStructure@kotlinx.serialization.encoding.Decoder(kotlinx.serialization.descriptors.SerialDescriptor;kotlin.Function1<kotlinx.serialization.encoding.CompositeDecoder,0:0>){0§<kotlin.Any?>}[0]
11731176
final inline fun <#A: kotlin/Any?> (kotlinx.serialization.encoding/Encoder).kotlinx.serialization.encoding/encodeCollection(kotlinx.serialization.descriptors/SerialDescriptor, kotlin.collections/Collection<#A>, crossinline kotlin/Function3<kotlinx.serialization.encoding/CompositeEncoder, kotlin/Int, #A, kotlin/Unit>) // kotlinx.serialization.encoding/encodeCollection|encodeCollection@kotlinx.serialization.encoding.Encoder(kotlinx.serialization.descriptors.SerialDescriptor;kotlin.collections.Collection<0:0>;kotlin.Function3<kotlinx.serialization.encoding.CompositeEncoder,kotlin.Int,0:0,kotlin.Unit>){0§<kotlin.Any?>}[0]

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

Lines changed: 59 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
package kotlinx.serialization.modules
66

77
import kotlinx.serialization.*
8+
import kotlinx.serialization.descriptors.*
89
import kotlinx.serialization.internal.*
910
import kotlin.reflect.*
1011

@@ -25,21 +26,70 @@ public class PolymorphicModuleBuilder<in Base : Any> @PublishedApi internal cons
2526

2627

2728
/**
28-
* Registers the child serializers for the sealed [subclass] [serializer] in the resulting module under the [base class][Base].
29+
* Registers the child serializers for the sealed [subclass] [serializer] in the resulting module under
30+
* the [base class][Base]. Please note that type `T` must be sealed, if not a runtime error will
31+
* be thrown at registration time. This function is a convenience function for the version that
32+
* receives a serializer.
33+
*
34+
*
35+
* Example:
36+
* ```kotlin
37+
* interface Base
38+
*
39+
* @Serializable
40+
* sealed interface Sub
41+
*
42+
* @Serializable
43+
* class Sub1: Sub
44+
*
45+
* serializersModule {
46+
* polymorphic(Base::class) {
47+
* subclassesOfSealed<Sub>()
48+
* }
49+
* }
50+
* ```
2951
*/
30-
public inline fun <reified T : Base> subclassesOf(): Unit =
31-
subclassesOf(serializer<T>())
52+
@ExperimentalSerializationApi
53+
public inline fun <reified T : Base> subclassesOfSealed(): Unit =
54+
subclassesOfSealed(serializer<T>())
3255

3356

3457
/**
35-
* Registers the subclasses of the given class as subclasses of the outer class. This currently requires `baseClass`
36-
* to be sealed.
58+
* Registers the subclasses of the given class as subclasses of the outer class. This currently
59+
* requires `serializer` to be sealed. If not a runtime error will be thrown at registration time.
60+
*
61+
* Example:
62+
* ```kotlin
63+
* interface Base
64+
*
65+
* @Serializable
66+
* sealed interface Sub
67+
*
68+
* @Serializable
69+
* class Sub1: Sub
70+
*
71+
* serializersModule {
72+
* polymorphic(Base::class) {
73+
* subclassesOfSealed(Sub.serializer())
74+
* }
75+
* }
76+
* ```
77+
*
78+
* It is an error if th
3779
*/
38-
public fun <T: Base> subclassesOf(serializer: KSerializer<T>) {
80+
@ExperimentalSerializationApi
81+
public fun <T: Base> subclassesOfSealed(serializer: KSerializer<T>) {
82+
// Note that the parameter type is `KSerializer` as `SealedClassSerializer` is an internal type
83+
// not available to users
3984
require(serializer is SealedClassSerializer) {
4085
"subClassesOf only supports automatic adding of subclasses of sealed types."
4186
}
4287
for ((subsubclass, subserializer) in serializer.class2Serializer.entries) {
88+
// This error would be caught by the Json format in its validation, but this is format specific
89+
require (subserializer.descriptor.kind != PolymorphicKind.OPEN) {
90+
"It is not possible to register subclasses of sealed types when those subclasses " +
91+
"themselves are (open) polymorphic, as this would represent an incomplete hierarchy"
92+
}
4393
@Suppress("UNCHECKED_CAST")
4494
// We don't know the type here, but it matches if correct in the sealed serializer.
4595
subclass(subsubclass as KClass<T>, subserializer as KSerializer<T>)
@@ -143,5 +193,6 @@ public inline fun <Base : Any, reified T : Base> PolymorphicModuleBuilder<Base>.
143193
/**
144194
* Registers the child serializers for the sealed class [T] in the resulting module under the [base class][Base].
145195
*/
146-
public inline fun <Base : Any, reified T : Base> PolymorphicModuleBuilder<Base>.subclassesOf(clazz: KClass<T>): Unit =
147-
subclassesOf(clazz.serializer())
196+
@ExperimentalSerializationApi
197+
public inline fun <Base : Any, reified T : Base> PolymorphicModuleBuilder<Base>.subclassesOfSealed(clazz: KClass<T>): Unit =
198+
subclassesOfSealed(clazz.serializer())

docs/polymorphism.md

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -415,9 +415,11 @@ fun main() {
415415
> Note: On Kotlin/Native, you should use `format.encodeToString(PolymorphicSerializer(Project::class), data))` instead due to limited reflection capabilities.
416416
417417
### Registering sealed children as subclasses
418-
A sealed parent interface or class can be used to directly register all its children using `subclassesOf`. This will
419-
expose all children that would be available when serializing the parent directly, but now as sealed. Please note that
420-
this is will remain open serialization, and the sealed parent serializer will not be used in serialization.
418+
A sealed parent interface or class can be used to directly register all its children using `subclassesOfSealed`.
419+
This will expose all children that would be available when serializing the parent directly, but now as sealed. Please
420+
note that this is will remain open serialization, and the sealed parent serializer will not be used in serialization.
421+
In addition, it is not valid if the hierarchy contains open (not sealed) polymorphic children (this will result in
422+
an error at runtime).
421423

422424
<!--- TEST LINES_START -->
423425

formats/json-tests/commonTest/src/kotlinx/serialization/features/PolymorphicSealedChildTest.kt

Lines changed: 27 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,10 +5,10 @@
55
package kotlinx.serialization.features
66

77
import kotlinx.serialization.*
8-
import kotlinx.serialization.json.Json
8+
import kotlinx.serialization.json.*
99
import kotlinx.serialization.modules.*
10-
import kotlinx.serialization.test.assertStringFormAndRestored
11-
import kotlin.test.Test
10+
import kotlinx.serialization.test.*
11+
import kotlin.test.*
1212

1313
class PolymorphicSealedChildTest {
1414

@@ -32,14 +32,37 @@ class PolymorphicSealedChildTest {
3232
data class Baz(val baz: String) : Foo()
3333
}
3434

35+
@Serializable
36+
sealed class FooOpen: FooBase() {
37+
@Serializable
38+
data class Bar(val bar: Int) : FooOpen()
39+
@Serializable
40+
abstract class Baz: FooOpen()
41+
@Serializable
42+
data class BazChild1(val baz: String) : Baz()
43+
@Serializable
44+
data class BazChild2(val baz: String) : Baz()
45+
}
46+
3547
val sealedModule = SerializersModule {
3648
polymorphic(FooBase::class) {
37-
subclassesOf<Foo>()
49+
subclassesOfSealed<Foo>()
3850
}
3951
}
4052

4153
val json = Json { serializersModule = sealedModule }
4254

55+
@Test
56+
fun testOpenGrandchildIsInvalid() {
57+
assertFailsWith<IllegalArgumentException> {
58+
SerializersModule {
59+
polymorphic(FooBase::class) {
60+
subclassesOfSealed<FooOpen>()
61+
}
62+
}
63+
}
64+
}
65+
4366
@Test
4467
fun testSaveSealedClassesList() {
4568
assertStringFormAndRestored(

0 commit comments

Comments
 (0)