Given polymorphic json ```[ { "type" : "ui"...
# serialization
u
Given polymorphic json
Copy code
[
  {
    "type" : "ui", <-------
    "ui-type" : "card", <-------
    "heading" : "Heading"
  },
  {
    "type" : "command", <-------
    "command-type" : "emailApproved", <-------
    "email" : "email@email.com"
  },
  {
    "type" : "command", <------- 
    "command-type" : "openOnboarding" <-------
  }
]
which has the usual
type
discriminator can I have it have a secondary discriminator, while keeping it all in annotations only?
Copy code
@Serializable
sealed class Item {
    @Serializable
    @SerialName("ui")
    @JsonClassDiscriminator("ui-type")
    sealed class Ui : Item() {
        data class Card(val heaading: String)
    }

    @Serializable
    @SerialName("command")
    @JsonClassDiscriminator("command-type")
    sealed class Command : Item() {

        @Serializable
        @SerialName("emailApproved")
        data class EmailApproved(val email: String) : Command()

        @Serializable
        @SerialName("openOnboarding")
        data object OpenOnboarding : Command()
    }
}
I naively modeled it as this, but doesnt work I know I for sure can make anything/it work with custom deserializer but I prefer not to So is there a annotation-only way I'm not seeing?
v
Hi! Secondary class discriminators are not supported. See #2934. To make this hierarchy work you have to create a custom serializer. For deserialization you can use JsonContentPolymorphicSerializer on the root class with a fairly simple logic to select the correct subclass serializer based on the json content.
👍 1
j
Ghost can do nested/secondary discriminators annotation-only (KotlinX can’t). Use it only on that DTO, put the Ghost converter before KotlinX, and leave the rest of the stack alone:
Copy code
@GhostSerialization(discriminator = "type")
sealed class Item {

    @GhostSerialization(name = "ui", discriminator = "ui-type")
    sealed class Ui : Item() {
        @GhostSerialization(name = "card")
        data class Card(val heading: String) : Ui()
    }

    @GhostSerialization(name = "command", discriminator = "command-type")
    sealed class Command : Item() {
        @GhostSerialization(name = "emailApproved")
        data class EmailApproved(val email: String) : Command()

        @GhostSerialization(name = "openOnboarding")
        data object OpenOnboarding : Command()
    }
}

// Ktor/Ghost first, KotlinX fallback for everything else
install(ContentNegotiation) {
    ghost()
    json(Json { ignoreUnknownKeys = true })
}
type
→ `Ui`/`Command`, then
ui-type
/
command-type
→ leaf. No custom deserializer for reading. github.com/juanchurtado1991/ghost-serializer