kotlinserializationdeserializationktor

KTOR - Derialize POST request body with nested object


I have an object

@Serializable
data class Manufacturer(
    val uuid: String? = null,
    val name: String,
    val cars: List<CarModel> = emptyList()
)

That is used to convert a request body into an internal object. CarModel is defined as follows:

@Serializable
data class CarModel(val uuid: String? = null, val carType: String, val name: String)

Now I can define a request body

{
    "name": "Test Name",
}

And the request is successful, however when I add a list representing car models, the request fails with the error: Failed to convert request body to class

Here's an example request body

{
    "name": "Manufacturer 1",
    "cars": [
        {
            "car_type": "Sedan",
            "name": "Car 1",
        },
        {
            "car_type": "Sedan",
            "name": "Car 2",
        },
    ]
}

It seems like ktor is having trouble deserializing nested objects, which is why when I provide a list of cars, it fails, but I can't figure out how to work around this, when the CarModel itself is serializable, so in theory, should be implicitly supported? I can successfully send list of strings, I just am unable to handle custom objects.

EDIT: Added Stack Trace

2024-12-03 09:40:24.588 [eventLoopGroupProxy-4-3] TRACE io.ktor.routing.Routing - Trace for [manufacturer]
/, segment:0 -> SUCCESS @ /
  /ws, segment:0 -> FAILURE "Selector didn't match" @ /ws
  /json, segment:0 -> FAILURE "Selector didn't match" @ /json
  /manufacturers, segment:0 -> FAILURE "Selector didn't match" @ /manufacturers
  /manufacturer, segment:1 -> SUCCESS @ /manufacturer
    /manufacturer/(method:POST), segment:1 -> SUCCESS @ /manufacturer/(method:POST)
  /tag, segment:0 -> FAILURE "Selector didn't match" @ /tag
Matched routes:
  "" -> "manufacturer" -> "(method:POST)"
Route resolve result:
  SUCCESS @ /manufacturer/(method:POST)
2024-12-03 09:40:24.650 [eventLoopGroupProxy-4-3] TRACE i.k.server.engine.DefaultTransform - No Default Transformations found for class io.ktor.utils.io.ByteBufferChannel and expected type TypeInfo(type=class com.carapp.models.Manufacturer, reifiedType=class com.carapp.models.Manufacturer, kotlinType=com.carapp.models.Manufacturer) for call /manufacturer
2024-12-03 09:40:24.689 [eventLoopGroupProxy-4-3] TRACE i.k.s.p.statuspages.StatusPages - Call /manufacturer failed with cause io.ktor.server.plugins.BadRequestException: Failed to convert request body to class com.carapp.models.Manufacturer
2024-12-03 09:40:24.714 [eventLoopGroupProxy-4-3] TRACE i.k.s.p.statuspages.StatusPages - Executing (io.ktor.server.application.ApplicationCall, kotlin.Throwable) -> kotlin.Unit for exception io.ktor.server.plugins.BadRequestException: Failed to convert request body to class com.carapp.models.Manufacturer for call /manufacturer
2024-12-03 09:40:24.715 [eventLoopGroupProxy-4-3] TRACE i.k.s.p.c.ContentNegotiation - Skipping because body is already converted.


Solution

  • As it turns out, the key I was using in the JSON when making the request to representCar.carType was incorrect - I was sending along car_type when I should have been sending carType as the JSON key, so the correct JSON body is actually:

    {
        "name": "Manufacturer 1",
        "cars": [
            {
                "carType": "Sedan",
                "name": "Car 1",
            },
            {
                "carType": "Sedan",
                "name": "Car 2",
            },
        ]
    }