Consider the following deserialization in Ktor setup context:
- A simple model to deserialize JSON requests (aims to receive/store only Strings):
@Serializable
data class TestingDTO(val texts: List<String>)
- The Ktor entry point:
fun main() {
embeddedServer(
Netty,
port = 8080,
host = "0.0.0.0",
module = Application::myApp
).start(wait = true)
}
fun Application.myApp() {
install(ContentNegotiation) {
json()
}
routing {
post("/testing") {
runCatching {
val receivedTestingDTO = call.receive<TestingDTO>()
println(receivedTestingDTO)
call.respond(HttpStatusCode.Created)
}.onFailure {
call.respond(HttpStatusCode.BadRequest, it.message ?: "deserialization problem!!!")
}
}
}
}
- Example of POST request to the bove setup:
curl -v -d '{"texts": ["foo", "bar", 1]}' -H 'Content-Type: application/json' http://127.0.0.1:8080/testing
Note: notice the JSON array containing strings and number at same time.
Now, if we run that, basically the call.receive<TestingDTO>() deserilizes the JSON of the request to the TestingDTO type "perfectly", even we puting a number inside that JSON array, causing the transformation of all items from that to String (in order to fit them in the target texts list?).
However if we use as deserialization method other kotlin API, the class kotlinx.serialization.json.Json, the example request fails as expected. For example, the runCatching block:
runCatching {
// now consuming request to read raw json as "text"
// and using kotlinx class to deserialize that text fails
// if in request exists a diferent type that doesn't fits the target
// TestingDTO List
val receivedTestingDTO = Json.decodeFromString<TestingDTO>(call.receiveText())
receivedTestingDTO.texts.forEach {
println("the value [$it] is instance of ${it::class}")
}
call.respond(HttpStatusCode.Created)
}.onFailure {
call.respond(HttpStatusCode.BadRequest, it.message ?: "deserialization problem!!!")
}
}
So, what exactly is happening here? Why those two different behaviours? Am I missing something about the call.receive<>() function specification?
If not, the "working" approach is worth of using?
This behavior is the result of the isLenient json configuration builder.
In Ktor the default Json configuration used for all request uppon installing
ContentNegotiationis:In your example you use
Json.decodeFromStringwithout configuration. By default,isLenientis false.You should do something like this
to have the same behavior as Ktor.
Note:
You can force one json configuration by providing your own builder in the json function.