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 fullKType
for a givenT
.
That confirms we can't do better than current behavior for that use case.