Hi, I'm running into an issue trying to define a c...
# serialization
a
Hi, I'm running into an issue trying to define a custom serializer for one of my models
MenuGroup
that has a property that's a
List<MenuGroup>
. When my custom initializer is first initialized, it attempts to create the
descriptor
, which then recursively calls
descriptor
again when it tries to register the element for that list property. Is there a way to get around this recursion that isn't a total hack? Here's a stripped down version of the models, serializer, and json I'm trying to format to (yes, the json format is weird but it's an external requirement).
Copy code
@Serializable(with = MenuGroupSerializer::class)
data class MenuGroup(
    val id: String,
    val name: String,
    val menuItems: List<MenuItem>,
    val menuGroups: List<MenuGroup>,
)

@Serializable
data class MenuItem(
    val id: String,
    val name: String,
    val priceInCents: Int,
)

object MenuGroupSerializer: KSerializer<MenuGroup> {
    override val descriptor = buildClassSerialDescriptor("MenuGroup") {
        element<String>("name")
        element<Map<String, MenuItem>?>("menuItems")
        element<Map<String, MenuGroup>?>("menuGroups")
    }

    override fun deserialize(decoder: Decoder): MenuGroup {
        TODO("Not yet implemented")
    }

    @OptIn(ExperimentalSerializationApi::class)
    override fun serialize(encoder: Encoder, value: MenuGroup) {
        encoder.encodeStructure(descriptor) {
            encodeStringElement(
                descriptor = descriptor,
                index = 0,
                value = value.name,
            )
            encodeNullableSerializableElement(
                descriptor = descriptor,
                index = 1,
                serializer = MapSerializer(String.serializer(), MenuItem.serializer()),
                value = value.menuItems.associateBy { it.name },
            )
            encodeNullableSerializableElement(
                descriptor = descriptor,
                index = 2,
                serializer = MapSerializer(String.serializer(), MenuGroup.serializer()),
                value = value.menuGroups.associateBy { it.name },
            )
        }
    }
}
Desired output
Copy code
{
    "menuGroups": {
        "Donuts": {
            "menuGroups": {},
            "menuItems": {
                "Glazed": {
                    "priceInCents": 100
                },
                "Chocolate": {
                    "priceInCents": 100
                }
            },
        },
        "Drinks": {
            "menuGroups": {},
            "menuItems": {
                "Coffee": {
                    "priceInCents": 100
                },
                "Tea": {
                    "priceInCents": 100
                }
            },
        }
    },
    "menuItems": {}
}
a
something that catches my eye is that you're passing in the descriptor of the structure when encoding the string elements - are you sure this is right?
Copy code
encoder.encodeStructure(descriptor) {
            encodeStringElement(
                descriptor = descriptor,
                index = 0,
                value = value.name,
            )
Instead of trying to write a custom serializer it's often easier to write additional Kotlin classes that matches the wire format more closely, and then converting to/from the Kotlin classes. So in your case, it looks like you'd want something like
Copy code
@Serializable
data class MenuGroupDelegate(
    val name: String,
    val menuItems: Map<String, MenuItemDelegate>,
    val menuGroups: Map<String, MenuGroupDelegate>,
)

@Serializable
data class MenuGroupDelegate(
    val priceInCents: Int,
)
Then KxS will auto-generate the descriptors, so you don't have to worry about the nitty gritty details.
1
a
Ah, perfect, I'll try that out!
👍 1