Another question: I need to deserialize an object ...
# serialization
d
Another question: I need to deserialize an object with a property that can either be a boolean or a nested object. How can I achieve this with kotlinx.serialization?
j
Is it something you have to do or just want to do? I wouldn't design an api that switches types like that if I know I am consuming it with an object oriented language.
d
I'm parsing OpenAPI Schema and it is specified like this, it's not my JSON
j
That is a shame indeed. I am unsure how polymorphic serialization works in this case, I use it with sealed classes and there it functions fine. I am thinking about if it is not better to investigate the network library in this case? Perhaps you can try to deserialize the incoming content in 2 different ways and see what happens
d
Well, that is what
JsonContentPolymorphicSerializer
lets me do. But OpenAPI Schema can come as JSON or YAML
j
but yaml is not supported anyway
you should use a 3rd party library as far as I know
d
kotlinx serialization is pluggable, there are 3rd party libraries, yes
I just wish there was a generic mechanism for this, instead of having to hard-code it for all formats
c
what part of openapi are you talking about? I think openapi has request and response but does it also have schema?
can you give an example of a field that can either be boolean or a object?
d
c
Wow. What a specification
d
Oh, if you want to really have fun, look at the code generator that takes this thing in. Its like the definition of Java enterprise hell (which is why I am working on my own now 🙃 )
c
is your version going to be open source, or is it already?
d
It will be, yes. There is barely anything there yet, I am just working on parsing the OpenAPI spec
c
why are you using kotlin serialization? is it cross platform?
d
Currently i'm only working on JVM but I plan to make it run on JS as well, because I plan to use it with typescript projects and npm is a more suitable distribution for that
Would you suggest something else?
c
weird schemas are probably easier to support with jackson
d
Yeah, maybe you are right
c
i use serialization on some projects but when i just want to parse some json quickly i just use jackson
and i heard that stuff like that is pretty easy with moshi but i havent used it yet
d
Yeah, unfortunately that is also JVM only
c
maybe start by supporting only json on the jvm and when it all works start building yaml and crossplatform
d
Yeah I think you've convinced me. This sucks 😄
a
Coming late to his thread, but it's quite an interesting subject. Isn't the problem really one of how to model the schema in Kotlin at all? Writing a custom serializer is fairly straightforward, whichever lib you go with, but how do you plan to represent this boolean|object union? Kotlin doesn't really have union types, so you will need to decide how to work around this. Once you have decided on your model, annotating the property with
@Serializable(with=MyCustomSerializer::class)
should let you do the necessary handling.
Off the top of my head, it seems that
Either<Boolean, MyNestedType>
might be a workable approach. (Arrow has an implementation of Either, but that might be a bit heavyweight for just that one type. Not hard to write your own.)
d
I model it with a sealed class with three subtypes: two objects for true/false and one data class for the object case.
And yes a custom serializer does let me do that, but its impossible to write this serializer without limiting myself to JSON
a
Something along the lines of this ought to do the job (very roughly):
Copy code
@Test
fun `union type serialization test`() {
    val json = Json { }
    expectThat(json.decodeFromString<Complicated>("""{ "union": true }"""))
        .get { union }
        .isA<Either.Left<Boolean>>()
        .get { left }.isEqualTo(true)

    expectThat(json.decodeFromString<Complicated>("""{ "union": "bob" }"""))
        .get { union }
        .isA<Either.Right<String>>()
        .get { right }.isEqualTo("bob")
}
This test passes with this code:
Copy code
@Serializable
data class Complicated(@Serializable(with = UnionSerializer::class) val union: Either<Boolean, String>)

class UnionSerializer(val leftSerializer: KSerializer<Boolean> = serializer(), val rightSerializer: KSerializer<String> = serializer()) : KSerializer<Either<Boolean, String>> {
    override fun deserialize(decoder: Decoder): Either<Boolean, String> {

        return either { decoder.decodeSerializableValue(rightSerializer) }.or { decoder.decodeSerializableValue(leftSerializer) }

    }

    override val descriptor: SerialDescriptor
        get() = TODO("Not yet implemented")

    override fun serialize(encoder: Encoder, value: Either<Boolean, String>) {
        TODO("Not yet implemented")
    }

}

fun <R> either(block: () -> R): Either<Exception, R> {
    return try {
        Either.Right(block())
    } catch (x: Exception) {
        Either.Left(x)
    }
}

fun <L, R> Either<Exception, R>.or(block: () -> L) : Either<L, R> {
    return mapLeft { block() }
}
Now, I've not tested this beyond what you see, but I can't see why it wouldn't work with any supported serialization format.
Admittedly, the try/catch approach is less ideal than peeking ahead at the next character, which would immediately put you into format-specific territory. Nulls could be tricky, too.
At the end of the day, you kinda have to ask the decoder "can I treat the element as a <x>?"
d
It would fail as soon as a format does not recover from exceptions properly. You are relying on the fact that you can just retry deserialization, but this only works here by "lucky chance" because it would fail immediately on the first deserialization call and the Json deserializer happens to be able to recover from that.
a
Perhaps. Is there another framework that gives you a way of handling the situation better?
d
Jackson is very configurable and I managed to write this very quickly with it - but its JVM only.
c
and with jackson it works also with yaml?
d
It sure does, without any changes to the serialization
c
cool, never used jackson for yaml