Spring Boot version: 2.5.0
Details
I have custom ObjectMapper
defined as:
@Bean
@Primary
public ObjectMapper jacksonMapper() {
ObjectMapper mapper = new ObjectMapper();
registerJacksonModules(mapper);
return mapper;
}
// ...
private void registerJacksonModules(ObjectMapper mapper) {
// ...
KotlinModule kotlinModule = new KotlinModule.Builder()
.singletonSupport(SingletonSupport.CANONICALIZE)
.build();
mapper.registerModule(kotlinModule);
}
When I use mapper myself and deserialize, say, a file, things are OK -- object
is re-used:
@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, property = "type")
@JsonSubTypes(
JsonSubTypes.Type(SomeConfigStrategy.Yes::class, name = "YES"),
JsonSubTypes.Type(SomeConfigStrategy.No::class, name = "NO"),
)
sealed class SomeConfigStrategy {
object Yes : SomeConfigStrategy()
object No : SomeConfigStrategy()
}
// ...
class SomeConfig @JvmOverloads constructor(
var param1: SomeConfigStrategy = SomeConfigStrategy.Yes,
)
// ...
val config: SomeConfig = mapper.readValue(configFile) // OK, param1 is the same instance as `Yes`/`No`
But when I try to load this config from URL this does not work and instances of Yes
/No
are created instead:
// server
@GetMapping("api/path")
fun constructSomeConfig(): SomeConfig {
return /* construction of config*/
}
// client
fun getConfig(): SomeConfig = restTemplate.getForObject(configUri) // BAD, param1 is NOT the same instance as `Yes`/`No`
Expected behavior
param1
must be deserialized as the same instance of Yes
/ No
because they are object
-s
Observer behavior
param1
is deserialized as new instance every time which is different from "static" SomeConfigStrategy.Yes
/ SomeConfigStrategy.No
Steps to reproduce
(I will create a minimalistic example later on, if needed)
Comment From: snicoll
@smedelyan thanks but a sample is needed as you're not showing on the RestTemplate
was built. I'd recommend not pasting a large amount of code snippet and go ahead with the sample directly.
Comment From: smedelyan
@snicoll uhm, it turned out I was building RestTemplate
with default message converters like:
return RestTemplateBuilder()
.setConnectTimeout(/* ... */)
.setReadTimeout(/* ... */)
.build()
This way it uses default list of converters and default mapper -- which, although, is configured with KotlinModule()
but singletonSupport
seems to have default value DISABLED
. I ended up injecting MappingJackson2HttpMessageConverter
from context (which has custom ObjectMapper
configured) and solved the issue:
@Bean
fun restTemplate(converters: MappingJackson2HttpMessageConverter): RestTemplate {
return RestTemplateBuilder()
.setConnectTimeout(/* ... */)
.setReadTimeout(/* ... */)
.messageConverters(converters) // <--- here
.build()
}
I'm closing the issue, thanks for your feedback.
Comment From: snicoll
Thanks for following up. FWIW, that's not the idiomatic way of configuring the RestTemplate
. You should inject the RestTemplateBuilder
rather than creating it yourself.