Hi, I have a very weird problem where I try to ser...
# serialization
a
Hi, I have a very weird problem where I try to serialize list of one polymorphed class (LootEntry) with a custom serializer, but I ALWAYS get an additionnal
"type": "kotlin.collections.ArrayList"
string value inside my list. I tried a thousands things to remove it but it still doesn't work, I don't understand how to remove it because I don't need it. When I use a custom serializer for the list that just serialize as a JsonArray of my LootEntries I still get the value. I even tried to create a JsonTransformingSerializer for my list but in this serializer I don't have the first string element when I print my elements. What am I doing wrong or what should I do to fix this ?
a
yeah, it’s awkward working with polymorphism. Are you using generics? Can you share an example of what you’ve made?
b
Code could definitely help. I know if you just mark an abstract/sealed class or interface with
@Serializable
(without specifying a custom serializer) it'll use
PolymorphicSerializer
, which adds that
type
entry: https://github.com/Kotlin/kotlinx.serialization/blob/v1.5.0/core/commonMain/src/kotlinx/serialization/PolymorphicSerializer.kt#L83 If you're using your own custom serializer though I'm not sure how that would happen
a
I will post code when I'm back at the office tomorrow 👍
Here is the LootEntry type :
Copy code
typealias LootEntry = @Serializable(LootEntrySerialized.Companion.LootEntrySerializer::class) LootEntrySerialized

@Serializable
sealed interface LootEntrySerialized {
	companion object {
		object LootEntrySerializer : KSerializer<LootEntrySerialized> by serializer() {
			override fun serialize(encoder: Encoder, value: LootEntrySerialized) {
				require(encoder is JsonEncoder) { "LootTable can only be serialized with Json" }

				encoder.encodeJsonElement(buildJsonObject {
					put("type", JsonPrimitive("minecraft:${value::class.simpleName!!.snakeCase()}"))
					Json.encodeToJsonElement(value).jsonObject.forEach { (key, value) ->
						if (key != "type") put(key, value)
					}
				})
			}
		}
	}
}
And here is one example of a LootEntry implementation :
Copy code
@Serializable
data class Empty(
	var quality: Int? = null,
	var weight: Int? = null,
) : LootEntry
And here is which object I try to serialize :
Copy code
@Serializable
data class LootPool(
	var rolls: NumberProvider,
	var bonusRolls: NumberProvider? = null,
	var conditions: PredicateConditions? = null,
	var entries: List<LootEntry>? = null,
	var functions: List<String>? = null,
)
b
You're only ever encoding and never decoding, right? Something like this might work for you
Copy code
/**
 * Serializes a polymorphic value with its concrete serializer directly,
 * instead of e.g. serializing an extra "type" discriminator entry.
 *
 * Only encoding is supported, and the [serializer] must be the default/generated polymorphic serializer.
 */
@OptIn(ExperimentalSerializationApi::class, InternalSerializationApi::class)
class DirectPolymorphicSerializer<T : Any>(serializer: KSerializer<T>) : KSerializer<T> {
    private val serializer = run {
        require(serializer is AbstractPolymorphicSerializer<T>) {
            "Serializer must be a ${AbstractPolymorphicSerializer::class.simpleName}"
        }
        serializer
    }

    // PolymorphicSerializer and SealedClassSerializer (the only implementors of AbstractPolymorphicSerializer
    // within kotlinx.serialization) both have their polymorphic descriptor element named `value`.
    private val valueDescriptor = serializer.descriptor.getElementDescriptor(
        serializer.descriptor.getElementIndex("value")
    )

    override val descriptor: SerialDescriptor = SerialDescriptor(
        "${DirectPolymorphicSerializer::class}<${this.serializer.baseClass}>",
        valueDescriptor
    )

    override fun serialize(encoder: Encoder, value: T) {
        val actualSerializer = serializer.findPolymorphicSerializer(encoder, value)

        val directActualSerializer = if (actualSerializer is AbstractPolymorphicSerializer) {
            DirectPolymorphicSerializer(actualSerializer)
        } else {
            actualSerializer
        }

        encoder.encodeSerializableValue(directActualSerializer, value)
    }

    override fun deserialize(decoder: Decoder): Nothing {
        throw SerializationException("${DirectPolymorphicSerializer::class.simpleName} only supports encoding")
    }
}
And similar to what you have, I got it working like this:
Copy code
@Serializable
sealed interface MySealedInterfaceSurrogate // Your actual sealed interface, like LootEntrySerialized

typealias MySealedInterface = @Serializable(MySealedInterfaceSerializer::class) MySealedInterfaceSurrogate

object MySealedInterfaceSerializer : KSerializer<MySealedInterface> by DirectPolymorphicSerializer(
    MySealedInterfaceSurrogate.serializer()
)
a
This actually doesn't work, I still get
"type": "kotlin.collections.ArrayList"
inside my list as the first element, but maybe it's my fault, I sent the wrong code. Here is the actual problem :
Copy code
@Serializable
data class LootPoolSerialized(
	var rolls: NumberProvider,
	var bonusRolls: NumberProvider? = null,
	var conditions: List<PredicateCondition>? = null,
	var entries: List<LootEntry>? = null,
	var functions: List<String>? = null,
)
When I try to serialize this class, with
conditions
being a list of one element that should serialize to
{"raining": true"}
, I get this :
Copy code
"conditions": [
    "type": "kotlin.collections.ArrayList",
    {
        "raining": true
    }
]
And currently I have in the PredicateCondition file this content :
Copy code
typealias PredicateCondition = @Serializable(PredicateConditionSerializer::class) PredicateConditionSurrogate

@Serializable
sealed interface PredicateConditionSurrogate

object PredicateConditionSerializer : KSerializer<PredicateCondition> by DirectPolymorphicSerializer(
    PredicateConditionSurrogate.serializer()
)
b
And how're you serializing it? Maybe directly like
Json.encodeToString(predicateCondition)
?
a
Yes exactly ! I'm just using a serializer module for other classes but it's working fine
b
Ohh, I've noticed that the typealias's @Serializable doesn't get picked up by that. If you pass PredicateConditionSerializer to encodeToString, or have the predicateCondition in a class property, I think it might work fine Edit: Poked around a bit and reported an issue
a
Actually I'm serializing a type containing a list of LootPools, so I'm pretty far from directly serializing one predicate condition