Skip to content

Add KotlinFeature to exclude compiler-generated $delegate backing fields from serialization #1102

@Hyung1Jung

Description

@Hyung1Jung

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

  1. @JsonIgnore on delegate field — Not possible in Kotlin (Can't annotate delegated properties #25)
  2. MixIn classes — Requires a separate MixIn per class, doesn't scale when by lazy is used across many classes
  3. @JsonIncludeProperties — Requires explicitly listing every property per class, fragile when fields are added/removed
  4. @param:JsonProperty(access = WRITE_ONLY) — Must be added to every lazy declaration individually
  5. Custom ValueSerializerModifier (current workaround) — Works, but every Kotlin project must independently discover and implement this same boilerplate

Additional context

Related issues: #459, #574, #471, #71, #25

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions