-
-
Notifications
You must be signed in to change notification settings - Fork 183
Description
Use case
We log entity classes as JSON before saving them to the database for auditing purposes. Our entities use Kotlin's by lazy
for computed properties, and our Jackson ObjectMapper is configured with field visibility ANY to serialize all fields
without requiring explicit annotations.
The problem is that compiler-generated $delegate backing fields leak into the serialized JSON output.
data class User(val id: Long, val name: String) {
val displayName: String by lazy { "$name#$id" }
}Serialized output:
{
"id": 1,
"name": "Alice",
"displayName": "Alice#1",
"displayName$delegate": {
"_initializer": {},
"_value": "Alice#1"
}
}The displayName$delegate field exposes Kotlin's internal implementation details — including the _initializer lambda
reference — into JSON output. Since $delegate fields are a Kotlin compiler artifact that Java never produces, and by lazy
is used pervasively in Kotlin, this belongs as a built-in option in the Kotlin module.
Describe the solution you'd like
Add a new KotlinFeature option to opt-in to automatic exclusion of $delegate fields:
val mapper = JsonMapper.builder()
.addModule(
KotlinModule.Builder()
.enable(KotlinFeature.IgnoreDelegateProperties)
.build()
)
.build()The implementation is minimal (~15 lines) — register a ValueSerializerModifier inside KotlinModule.setupModule() when the feature is enabled:
if (ignoreDelegateProperties) {
context.addBeanSerializerModifier(object : ValueSerializerModifier() {
override fun changeProperties(
config: SerializationConfig,
beanDesc: BeanDescription.Supplier,
beanProperties: MutableList<BeanPropertyWriter>
): MutableList<BeanPropertyWriter> {
beanProperties.removeIf { it.name.endsWith("\$delegate") }
return beanProperties
}
})
}Opt-in, disabled by default — fully backward-compatible
No new dependencies — simple name-suffix check, no kotlinx-metadata-jvm needed
-No AnnotationIntrospector changes — uses ValueSerializerModifier (addresses the concern from #459 about AI's property-by-property design)
- Follows existing patterns — fits naturally alongside other KotlinFeature options like NullToEmptyCollection and SingletonSupport
I'd be happy to submit a PR if this approach is acceptable.
Describe alternatives you've considered
- @JsonIgnore on delegate field — Not possible in Kotlin (Can't annotate delegated properties #25)
- MixIn classes — Requires a separate MixIn per class, doesn't scale when
by lazyis used across many classes - @JsonIncludeProperties — Requires explicitly listing every property per class, fragile when fields are added/removed
- @param:JsonProperty(access = WRITE_ONLY) — Must be added to every lazy declaration individually
- Custom ValueSerializerModifier (current workaround) — Works, but every Kotlin project must independently discover and implement this same boilerplate