How do I use sealed interfaces for union types properly? (thread)
k
How do I use sealed interfaces for union types properly? (thread)
2
Copy code
@Serializable(with = ValueDeserializer::class)
sealed interface Value

@JvmInline
@Serializable
value class NumberValue(val value: Int) : Value

@JvmInline
@Serializable
value class StringValue(val value: String) : Value

object ValueDeserializer :
    JsonContentPolymorphicSerializer<Value>(Value::class) {
    override fun selectDeserializer(element: JsonElement): DeserializationStrategy<out Value> {
        if (element !is JsonPrimitive) throw RuntimeException("failed to deserialize literal value")
        return if (element.jsonPrimitive.isString) StringValue.serializer() else NumberValue.serializer()
    }
}

@Test
fun `deserializes from literal value`() {
    val inputs = listOf(
        ("42", NumberValue(42)),
        ("\"42\"", StringValue("42")),
    )
    inputs.forEach { 
        // Check that the class matches.
        val decoded = Json.decodeFromString<Value>(it.first)
        assertEquals(decoded is it.second::class, true)
    }
}
I get this error. The goal is to have a way to describe fields that can either be a string or a number. I need to know the type at runtime and can't use transformation to convert both to one type only.
Copy code
Serializer for class 'Value' is not found.
Mark the class as @Serializable or provide the serializer explicitly.
kotlinx.serialization.SerializationException: Serializer for class 'Value' is not found.
Mark the class as @Serializable or provide the serializer explicitly.
Kotlin 1.6.10 Kotlin Serialization 1.3.3 Android Project, Source Compatibility 1.8, Java 11 (Stacktrace mentions java.base@11.0.11)
it should work in another structure, e.g.
Copy code
@Serializable
data class Container(val value: Value)
but currently the serializer lookup for
sealed interface
is broken in cases such as yours
k
Wouldn't that wrap the
Value
inside of an object, i.e.,
"42"
wouldn't work, it would have to be
{"value":"42"}
e
Copy code
Json.decodeFromString(ValueDeserializer, it)
yes, it would wrap the value inside another object, but I find it rare that you have a top-level primitive to parse
k
I have JSON of the form of
{"label": "hello-world", "value": <"string" or number>}
. And I tried to model that value type above.
Maybe there is another way I can realize this. I only need deserialization though.
e
then your real use case looks like
Copy code
@Serializable
data class Container(val label: String,  val value: Value)
doesn't it? that should work
k
haha true, that should work! I wanted to unit test the deserialization of field. Building bottom to top. Thanks for the help!
e
as an aside, not all JsonPrimitive are string or int: in general JSON numbers can be double, and boolean and null are also primitive
🙌🏾 1
🙌 1