snowe
07/09/2021, 1:36 AM@Serializable
data class ChangeOrderInput(
val oldProposal: OldProposal,
val newProposals: List<NewProposal>
)
@Serializable
data class OldProposal(
val product: Product
)
@Serializable
data class NewProposal(
val id: String,
val product: Product,
val changeOrderType: ChangeOrderType? = null
)
@Serializable(with = ProductSerializer::class)
sealed class Product {
abstract val type: ProductType
abstract val bundle: ProductBundle
}
object ProductSerializer : JsonContentPolymorphicSerializer<Product>(Product::class) {
override fun selectDeserializer(element: JsonElement): DeserializationStrategy<out Product> {
val typeString = element.jsonObject["type"]?.jsonPrimitive?.content ?: error("`type` wasn't provided for `product`")
return when (ProductType.valueOf(typeString)) {
ProductType.Cash -> CashProduct.serializer()
<http://ProductType.Loan|ProductType.Loan> -> LoanProduct.serializer()
}
}
}
@Serializable
data class CashProduct(
override val type: ProductType = ProductType.Cash,
override val bundle: ProductBundle,
val totalPrice: BigDecimal
) : Product()
@Serializable
data class LoanProduct(
override val type: ProductType = <http://ProductType.Loan|ProductType.Loan>,
override val bundle: ProductBundle,
val vendor: String,
val term: BigDecimal,
val apr: BigDecimal,
val totalPrice: BigDecimal
) : Product()
@Serializable
data class ExampleResponse(
val version: String = "",
val outputObjects: List<NewProposal>? = listOf()
)
When I serialize a ChangeOrderInput object:
val inputJsonString = input.reader().use { it.readText() }
val request = Serialization.json.decodeFromString(ChangeOrderInput.serializer(), inputJsonString)
val response = runBlocking {
changeOrderWrapper.handleRequest(request)
}
val outputJsonString = Serialization.json.encodeToString(response)
output.writer().use { it.write(outputJsonString) }
it serializes both the oldProposal and newProposal correctly (see request object in the image).
But when I try to deserialize the ExampleResponse
object, it deserializes everything except the NewProposal#type
field.
I just now thought that maybe this was due to the classDiscriminator property on the Serializer, but that is not it. I tried changing it to #class
and it still has the same result.
Anyone know what is going on here?snowe
07/09/2021, 1:37 AMobject Serialization {
val json: Json by lazy {
Json {
ignoreUnknownKeys = true
isLenient = true
classDiscriminator = "#class"
}
}
}
ephemient
07/09/2021, 5:11 PMencodeDefaults = false
by defaultsnowe
07/09/2021, 5:11 PMtype
field.ephemient
07/09/2021, 5:13 PM@Serializable
sealed class Product
@Serializable @SerialName("Cash")
class CashProduct : Product()
@Serializable @SerialName("Loan")
class LoanProduct : Product()
snowe
07/09/2021, 5:14 PMsnowe
07/09/2021, 5:14 PM@SerialName(..)
doing there?ephemient
07/09/2021, 5:14 PMsnowe
07/09/2021, 5:15 PMsnowe
07/09/2021, 5:15 PMJsonContentPolymorphicSerializer
at all?ephemient
07/09/2021, 5:17 PMephemient
07/09/2021, 5:18 PMoverride val type get() = ProductType.Cash
etc. in the body if you find it useful)snowe
07/09/2021, 5:19 PMCaused by: kotlinx.serialization.json.internal.JsonDecodingException: Polymorphic serializer was not found for missing class discriminator ('null')
JSON input: {"type":"Cash","bundle":"Base","totalPrice":"1.23"}
where it looks like it's still trying to deserialize nested objects with the Product
serializer.ephemient
07/09/2021, 5:19 PM#class
?snowe
07/09/2021, 5:20 PMsnowe
07/09/2021, 5:24 PMCaused by: java.lang.IllegalStateException: Sealed class 'Loan' cannot be serialized as base class 'com.sunrun.changeorder.resources.Product' because it has property name that conflicts with JSON class discriminator 'type'. You can either change class discriminator in JsonConfiguration, rename property with @SerialName annotation or fall back to array polymorphism
at kotlinx.serialization.json.internal.PolymorphicKt.validateIfSealed(Polymorphic.kt:46)
at kotlinx.serialization.json.internal.PolymorphicKt.findActualSerializer(Polymorphic.kt:30)
at kotlinx.serialization.json.internal.PolymorphicKt.access$findActualSerializer(Polymorphic.kt:1)
at kotlinx.serialization.json.internal.StreamingJsonEncoder.encodeSerializableValue(StreamingJsonEncoder.kt:273)
at kotlinx.serialization.encoding.AbstractEncoder.encodeSerializableElement(AbstractEncoder.kt:80)
at com.sunrun.changeorder.resources.NewProposal.write$Self(Rest.kt:27)
at com.sunrun.changeorder.resources.NewProposal$$serializer.serialize(Rest.kt:27)
at com.sunrun.changeorder.resources.NewProposal$$serializer.serialize(Rest.kt:27)
ephemient
07/09/2021, 5:33 PMtype
from the constructors, e.g.
data class CashProduct(/* no type */) : Product() {
override val type: ProductType // or just remove this property altogether, if you don't need it
get() = ProductType.Cash
}
as I mentioned earliersnowe
07/09/2021, 5:36 PMCashProduct
, then create cash product without the type in the constructor, then I can have that property to still be able to get the given type?ephemient
07/09/2021, 5:36 PMephemient
07/09/2021, 5:37 PMdata class CashProduct(@Transient val type: ProductType) : Product()
but IMO it's just confusing to allow it to be overridden from code when it won't be serializedephemient
07/09/2021, 5:39 PMCashProduct(type = <http://ProductType.LOAN|ProductType.LOAN>)
-> serialize -> deserialize -> doesn't make sense, regardless of which approach you usedsnowe
07/09/2021, 5:39 PMsnowe
07/09/2021, 5:40 PMsnowe
07/09/2021, 5:40 PMsnowe
07/09/2021, 5:40 PMsnowe
07/09/2021, 5:41 PM@Polymorphic
annotation. What is that? Why do I not need it? https://kotlin.github.io/kotlinx.serialization/kotlinx-serialization-core/kotlinx-serialization-core/kotlinx.serialization/-polymorphic-serializer/index.htmlephemient
07/09/2021, 5:41 PMephemient
07/09/2021, 5:42 PMBaseRequest
is a not a sealed class
snowe
07/09/2021, 5:42 PMsnowe
07/09/2021, 5:42 PMephemient
07/09/2021, 5:42 PMsnowe
07/09/2021, 5:43 PMsnowe
07/09/2021, 5:44 PMoverride val type: ProductType
get() = <http://ProductType.Loan|ProductType.Loan>
work but
override val type: ProductType = <http://ProductType.Loan|ProductType.Loan>
results in
org.jboss.resteasy.spi.UnhandledException: java.lang.IllegalStateException: Sealed class 'Loan' cannot be
serialized as base class 'com.sunrun.changeorder.resources.Product' because it has property name that conflicts
with JSON class discriminator 'type'. You can either change class discriminator in JsonConfiguration, rename
property with @SerialName annotation or fall back to array polymorphism
?
Functionally they are the same are they not? Or is it because the latter results in setting the property on instantiation, while the former is just a getter, not set on instantiation?ephemient
07/09/2021, 5:45 PM@Transient
, not serialized), while the latter has both a backing field and a getterephemient
07/09/2021, 5:46 PMsnowe
07/09/2021, 5:47 PMephemient
07/09/2021, 5:47 PMsnowe
07/09/2021, 5:47 PMMoroa Matshubeni
04/24/2024, 7:08 PMSealed class cannot be serialized as base class because it has property name that conflicts with JSON class discriminator 'type'. You can either change class discriminator in JsonConfiguration, rename property with @SerialName annotation or fall back to array polymorphism