Jackson configuration is cumbersome
See original GitHub issueI need to register jackson-module-kotlin with the Temporal SDK in order to support Kotlin data classes. As far as Jackson goes, this is accomplished with a fairly trivial modification to the ObjectMapper
. However, the Temporal SDK currently makes it very cumbersome to get a hook onto that reference.
While it’s possible to declare your entirely own DataConverter
, it requires replicating a large portion of the default Temporal configuration. E.g., here is the code I would need to write if I just wanted to make one simple Jackson modification:
// Replicate the default Jackson configuration
val mapper = ObjectMapper()
mapper.configure(DeserializationFeature.ADJUST_DATES_TO_CONTEXT_TIME_ZONE, false)
mapper.configure(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS, false)
mapper.registerModule(JavaTimeModule())
mapper.registerModule(Jdk8Module())
mapper.setVisibility(PropertyAccessor.FIELD, JsonAutoDetect.Visibility.ANY)
// Enable my Jackson customization
mapper.registerKotlinModule()
// Replicate the default payload converters (in order) while overriding JacksonJsonPayloadConverter
val dataConverter = DefaultDataConverter(
NullPayloadConverter(),
ByteArrayPayloadConverter(),
ProtobufJsonPayloadConverter(),
ProtobufPayloadConverter(),
JacksonJsonPayloadConverter(mapper)
)
// Register the global default
GlobalDataConverter.register(dataConverter)
This is a lot of boilerplate and prone to falling out of sync with the defaults that we’re copy/pasting.
Alternatively, I can use reflection to modify the ObjectMapper
that is already registered globally. E.g.,
val jacksonConverter = DefaultDataConverter.STANDARD_PAYLOAD_CONVERTERS.first { it is JacksonJsonPayloadConverter }
val mapperField = JacksonJsonPayloadConverter::class.java.getDeclaredField("mapper")
mapperField.isAccessible = true
val mapper = mapperField.get(jacksonConverter) as ObjectMapper
mapper.registerKotlinModule()
This is of course hacky, but I actually prefer it since I know my settings won’t drift out of sync from Temporal’s default configuration, and I’ll get an explicit failure if something breaks with the reflection.
It would be nice if it was simpler and easier to be able to customize the Jackson-level configuration. Some potential solutions:
- Similar to exposing a global
DataConverter
, expose a globalObjectMapper
- Expose properties for
List<PayloadConverter>
andObjectMapper
that would allow a user to drill down into the current mapper to modify it (this still feels a little hacky, since I would rather treat the current mapper as immutable) - Expose something like a
UnaryOperator<ObjectMapper>
that allows a user to customize the mapper alongside Temporal’s existing default configuration - Expose an environment variable that will trigger Temporal to call
mapper.registerKotlinModule()
- Expose an environment variable that will trigger Temporal to call
mapper.findAndRegisterModules()
Related:
- https://community.temporal.io/t/hi-deserialization-problem/474/4
- https://github.com/temporalio/sdk-java/issues/139
Version: 1.16.0
Issue Analytics
- State:
- Created a year ago
- Comments:8 (4 by maintainers)
@Bennett-Lynch You may also want to check out this class from our kotlin module: https://github.com/temporalio/sdk-java/blob/master/temporal-kotlin/src/main/kotlin/io/temporal/common/converter/KotlinObjectMapperFactory.kt
Offering automatic configuration via class-path module detection is a very convenient utility. Without it, I’ll now be littering any project using Temporal and Kotlin with manual overrides in both prod/test configuration (which are somewhat cumbersome to insert, due to their global nature). In my case, I’m perfectly happy with the default serialization configuration otherwise. If including such detection in
temporal-kotlin
is too risky, I personally would love to have a separate and dedicated module option, liketemporal-kotlin-jackson
. Just my 2 cents, but I respect your opinion as the maintainer.