d

    diesieben07

    1 year ago
    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?
    I found
    JsonContentPolymorphicSerializer
    , but that doesn't work if I also need to support Yaml (or similar) input. And it is a hard fail without a way to provide a separate implementation for yaml. It would be nice if we could have something like a
    PeekableDecoder
    with a way to try (and then rewind) a serialization.
    j

    Joost Klitsie

    1 year ago
    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

    diesieben07

    1 year ago
    I'm parsing OpenAPI Schema and it is specified like this, it's not my JSON
    j

    Joost Klitsie

    1 year ago
    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

    diesieben07

    1 year ago
    Well, that is what
    JsonContentPolymorphicSerializer
    lets me do. But OpenAPI Schema can come as JSON or YAML
    j

    Joost Klitsie

    1 year ago
    but yaml is not supported anyway
    you should use a 3rd party library as far as I know
    d

    diesieben07

    1 year ago
    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
    christophsturm

    christophsturm

    1 year ago
    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

    diesieben07

    1 year ago
    christophsturm

    christophsturm

    1 year ago
    Wow. What a specification
    d

    diesieben07

    1 year ago
    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 🙃 )
    christophsturm

    christophsturm

    1 year ago
    is your version going to be open source, or is it already?
    d

    diesieben07

    1 year ago
    It will be, yes. There is barely anything there yet, I am just working on parsing the OpenAPI spec
    christophsturm

    christophsturm

    1 year ago
    why are you using kotlin serialization? is it cross platform?
    d

    diesieben07

    1 year ago
    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?
    christophsturm

    christophsturm

    1 year ago
    weird schemas are probably easier to support with jackson
    d

    diesieben07

    1 year ago
    Yeah, maybe you are right
    christophsturm

    christophsturm

    1 year ago
    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

    diesieben07

    1 year ago
    Yeah, unfortunately that is also JVM only
    christophsturm

    christophsturm

    1 year ago
    maybe start by supporting only json on the jvm and when it all works start building yaml and crossplatform
    d

    diesieben07

    1 year ago
    Yeah I think you've convinced me. This sucks 😄
    a

    adk

    1 year ago
    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

    diesieben07

    1 year ago
    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

    adk

    1 year ago
    Something along the lines of this ought to do the job (very roughly):
    @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:
    @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

    diesieben07

    1 year ago
    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

    adk

    1 year ago
    Perhaps. Is there another framework that gives you a way of handling the situation better?
    d

    diesieben07

    1 year ago
    Jackson is very configurable and I managed to write this very quickly with it - but its JVM only.
    christophsturm

    christophsturm

    1 year ago
    and with jackson it works also with yaml?
    d

    diesieben07

    1 year ago
    It sure does, without any changes to the serialization
    christophsturm

    christophsturm

    1 year ago
    cool, never used jackson for yaml