Affects: 6.1.12
Hi all, I'm using spring-web in Kotlin as a REST client and I encountered some issues when trying to serialize a MutableList I prepared a sample code to show the issue:
fun main() {
val client = RestTemplate(listOf(
KotlinSerializationJsonHttpMessageConverter()
))
val data = mutableListOf<String>()
data.add("1")
data.add("2")
val entity = HttpEntity(data, HttpHeaders().apply {
contentType = MediaType.APPLICATION_JSON
})
val response = client.exchange("https://httpbin.org/post", HttpMethod.POST, entity, String::class.java)
logger.info(response.body)
}
But when I run this code, I get this error:
No HttpMessageConverter for java.util.ArrayList and content type "application/json"
I assumed KotlinSerializationJsonHttpMessageConverter Would be able to convert this to ["1", "2"], but it doesn't seem to work
Comment From: sdeleuze
That's due to the combination of a RestTemplate API limitation which, if I am not mistaken, does not allow to specify the request body type via a ParameterizedTypeReference and a https://github.com/Kotlin/kotlinx.serialization/ limitation with does not allow the serialization of ArrayList<*> (type erasure) with the default configuration.
On Spring side, to handle this use case I recommend switching from RestTemplate to RestClient API and do something like:
fun main(args: Array<String>) {
val client = RestClient.builder().messageConverters {
it.clear() // I will likely add the capability to provide directly a list of converters to avoid that inefficient default init + clear
it.add(StringHttpMessageConverter())
it.add(KotlinSerializationJsonHttpMessageConverter())
}.build()
val data = mutableListOf<String>()
data.add("1")
data.add("2")
println(client.post().uri("https://httpbin.org/post")
.bodyWithType<List<String>>(data).contentType(MediaType.APPLICATION_JSON)
.retrieve().body<String>())
}
Alternatively if you really need to stay with RestTemplate, you can potentially customize the configuration of the KotlinSerializationJsonHttpMessageConverter with the Json parameter customizing the polymorphic serialization of ArrayList<*> (I have not tried but could be possible).
Comment From: sdeleuze
See #33536 for the improved message converters initialization.
Comment From: ChananM
Thanks @sdeleuze
I eventually switched to Jackson which works much better with RestTemplate.
As a side note, I don't think there's any issue with the kotlinx.serialization since using Json.encodeToString on the array works fine, even when serializing <*> type erasure.
I also tried customizing KotlinSerializationJsonHttpMessageConverter by inheriting it and creating a custom converter that should work on any class and any content type, but unfortunately that also didn't work and I couldn't figure out why.
Comment From: sdeleuze
I am unsure as well, there may be some compile-time type parameter detection done by the Kotlin compiler in Json.encodeToString case that are not possible with the reflection based arrangement. I have asked a feedback to the Kotlin team and will let you know.
Comment From: sdeleuze
Feedback from Kotlin team
The reason that
Json.encodeToString(data)works is that it is an inline function with reified parameter, and it usestypeOf()under the hood.typeOf<T>()is essentially a compiler intrinsic that is able to build a fullKTypefor a givenT.
That confirms we can't do better than current behavior for that use case.