hello I would like to receive a generic list ```va...
# ktor
t
hello I would like to receive a generic list
Copy code
val updates = call.receive<List<PositionPatch>>()
however I receive
List<LinkedHashMap
. when I send / receive just one object, then it works like a charm. is there a way to influence the binding?
e
What deserializer are you using?
t
I am using Jackson, currently as a hack I read String and deserialize manually
e
As in you’re doing something like:
Copy code
val updatesString = call.receive<String>()
val updates = jacksonObjectMapper.read<List<PostitionPatch>>()
Or something else?
Because if you’re doing that you just need to add the Jackson deserializer in for the content negotiation of your Ktor server and do the call.receive() as you posted initially
t
I have this already
Copy code
install(ContentNegotiation) {
        jackson {
            findAndRegisterModules()
            enable(SerializationFeature.INDENT_OUTPUT)
            configure(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS, false)
        }
    }
but when I do
Copy code
val updates = call.receive<List<PositionPatch>>()
then I receive a LinkedHashMap inside of the list
so my hack for now is this
Copy code
val mapper = jacksonObjectMapper()
val updateString = call.receive<String>()
val type = TypeFactory
   .defaultInstance()
   .constructParametricType(List::class.java, PositionPatch::class.java)

val updates = mapper.readValue(updateString, type) as List<PositionPatch>
e
What does the definition for PositionPatch look like? Also, are you sure Jackson is getting loaded with the Kotlin module? The Jackson kotlin module helps deal with type erasure via reified generics and I haven’t had problems with that before
t
in the content negotiation part the modules are loaded
Copy code
install(ContentNegotiation) {
        jackson {
            findAndRegisterModules()
in gradle I have
Copy code
implementation "com.fasterxml.jackson.module:jackson-module-kotlin:$jackson_version"
definition of position patch is
Copy code
@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, include = JsonTypeInfo.As.PROPERTY, property = "type")
@JsonSubTypes(
    JsonSubTypes.Type(name = "update-category", value = UpdateCategory::class),
    JsonSubTypes.Type(name = "update-comment", value = UpdateComment::class)
)
sealed class PositionPatch(
    val type: String
)

@JsonTypeName("update-category")
data class UpdateCategory(val category: Category?) : PositionPatch("update-category")
@JsonTypeName("update-comment")
data class UpdateComment(val comment: String?) : PositionPatch("update-comment")
e
Alright, I’m gonna make a toy app a little later and see if I can reproduce this 🙂
t
thank you
e
Also, what version of Ktor are you on?
t
Copy code
ext.kotlin_version = '1.3.61'
    ext.jackson_version = '2.10.1'
    ext.koin_version = '2.0.1'
👍 1
ktor is 1.2.6
minimal-serialization-example.zip
there is a call in
backend-tests.http
with which you can test
e
Alright, I figured it out
Your issue is just the way the Jackson deserializer is written. They include the type (including generic type params) in the call, but they don't pass that along to Jackson for deserialization. Here's the code from the serializer in question:
Copy code
override suspend fun convertForReceive(context: PipelineContext<ApplicationReceiveRequest, ApplicationCall>): Any? {
        val request = context.subject
        val type = request.type
        val value = request.value as? ByteReadChannel ?: return null
        val reader = value.toInputStream().reader(context.call.request.contentCharset() ?: Charsets.UTF_8)
        return objectmapper.readValue(reader, type.javaObjectType)
    }
type.javaObjectType
in this case refers to
java.util.list
with the generics erased. However, the subject has a
typeInfo
property with the full reified generic type info that just isn't being used. I'd open an issue on the Ktor github or YouTrack.
For now you'll just need to continue using your hack. By the way, with the kotlin module installed you don't need to create a generic type factory like you're doing, you can just use
ObjectMapper.readValue<List<PositionPatch>>()
via the extension method from the Jackson Kotlin Module and it will still work.
As an example:
Copy code
import com.fasterxml.jackson.databind.*
import com.fasterxml.jackson.module.kotlin.readValue
import com.fasterxml.jackson.module.kotlin.registerKotlinModule

// ...

post("/sample-receive/list") {
    val receivedList = call.receive<String>()
    val om = ObjectMapper().registerKotlinModule()
    println(om.readValue<List<PositionPatch>>(receivedList))
    call.respond(HttpStatusCode.OK)
}
t
thank you for looking into it
(sorry for the late answer, being quite sick lately)