Describe the bug
When using the polymorphic serializer, if a class has a property called type, it will be serialized with two keys called type -- one with the class name, and one with the value of the class property.
To Reproduce
class SampleTests {
@Serializable
abstract class Base
@Serializable
data class Derived(val type: String) : Base()
@Test
fun test() {
val j = Json {
serialModule = SerializersModule {
polymorphic(Base::class) {
addSubclass(Derived.serializer())
}
}
}
val derived = Derived("string")
val stringified = j.stringify(Base.serializer(), derived)
val parsed = j.parse(Base.serializer(), stringified) // throws
assertEquals(
derived,
parsed
)
}
}
Expected behavior
An error should be emitted that the key type is reserved, with instructions to use SerialName. It could also be useful to allow the discriminator property name to be configurable
Environment
Discriminator name is configurable, see in JsonConfiguration class
The system that receives my json output runs validation and rejects unknown fields - so I can't use the discriminator to simply rename the default "type" field to something else.
Is there a way to configure the polymorphic serializer to use my "type" field, instead of the auto generated fully-qualified class name?
Value for the discriminator field is assigned from @SerialName on class (which is FQ name by default). You can reassign them individually, but unfortunately, there is no global policy yet.
So as an example I"ll reference use case in 380 with an added "type" field
with 0.11, we can now do the following:
@Serializable
sealed class Shape(val type: String)
@Serializable
data class Circle(val radius: Double) : Shape("CIRCLE")
@Serializable
data class Rectangle(val width: Double) : Shape("RECTANGLE")
@Serializable
data class Box(val shapes: List<@Polymorphic Shape>)
private val shapeModule = SerializersModule {
polymorphic(Shape::class) {
Circle::class with Circle.serializer()
Rectangle::class with Rectangle.serializer()
}
}
val box = Box(listOf(Circle(3.5), Rectangle(4.2)))
val json = Json(context = shapeModule).stringify(Box.serializer(), box)
this yields the following output:
{
"shapes": [
{
"type":"com.example.shape.Circle",
"type": "CIRCLE",
"radius": 3.5
},
{
"type":"com.example.shape.Rectangle",
"type": "RECTANGLE",
"width": 4.2
}
]
}
I understand that we can rename the class discriminator field to something else, so that the output would instead be "something_else": "com.example.shape.Circle", but is there way to tell the kotlin serializer how to use our existing "type" field? Something like:
private val shapeModule = SerializersModule {
polymorphic(Shape::class) {
Circle::class with Circle.serializer() on "CIRCLE"
Rectangle::class with Rectangle.serializer() on "RECTANGLE"
}
}
the goal being to have the output:
{
"shapes": [
{
"type": "CIRCLE",
"radius": 3.5
},
{
"type": "RECTANGLE",
"width": 4.2
}
]
}
Nevermind, I just understood what you meant.
The following yields exactly what I need. Awesome!
@Serializable
sealed class Shape
@Serializable
@SerialName("CIRCLE")
data class Circle(val radius: Double) : Shape()
@Serializable
@SerialName("RECTANGLE")
data class Rectangle(val width: Double) : Shape()
Now sealed classes are supported automatically and there is the same problem:
package com
import kotlinx.serialization.Serializable
import kotlinx.serialization.json.Json
import kotlinx.serialization.json.JsonConfiguration
@Serializable
sealed class Base
@Serializable
data class Child(val type: String) : Base()
object Tmp {
@JvmStatic
fun main(args: Array<String>) {
val json = Json(JsonConfiguration.Stable)
val child = Child("1")
val string = json.stringify(Base.serializer(), child)
println(string)
val parsed = json.parse(Base.serializer(), string)
println(parsed)
}
}
The output:
{"type":"com.Child","type":"1"}
Exception in thread "main" kotlinx.serialization.SerializationException: 1 is not registered for polymorphic serialization in the scope of class com.Base
at kotlinx.serialization.internal.AbstractPolymorphicSerializerKt.throwSubtypeNotRegistered(AbstractPolymorphicSerializer.kt:99)
at kotlinx.serialization.internal.AbstractPolymorphicSerializerKt.access$throwSubtypeNotRegistered(AbstractPolymorphicSerializer.kt:1)
at kotlinx.serialization.internal.AbstractPolymorphicSerializer.findPolymorphicSerializer(AbstractPolymorphicSerializer.kt:83)
at kotlinx.serialization.SealedClassSerializer.findPolymorphicSerializer(SealedSerializer.kt:92)
at kotlinx.serialization.json.internal.PolymorphicKt.decodeSerializableValuePolymorphic(Polymorphic.kt:41)
at kotlinx.serialization.json.internal.StreamingJsonInput.decodeSerializableValue(StreamingJsonInput.kt:33)
at kotlinx.serialization.CoreKt.decode(Core.kt:80)
at kotlinx.serialization.json.Json.parse(Json.kt:126)
at com.Tmp.main(Tmp.kt:24)
Most helpful comment
Discriminator name is configurable, see in
JsonConfigurationclass