FasterXML / FasterXML/jackson-module-kotlin

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

Offen
#1,102 8 Kommentare 1 Reaktion 0 zugewiesene Personen Auf GitHub ansehen
enhancement
Vorherrschende Sprache
Kotlin
Sterne
1.2k
Forks
187
Ø Merge
9 Std. 48 Min.
Gemergte PRs (30 T.)
16

Beschreibung

### 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.

```kotlin
data class User(val id: Long, val name: String) {
val displayName: String by lazy { "$name#$id" }
}
```

Serialized output:

```kotlin
{
"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:

```kotlin
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:

```kotlin
if (ignoreDelegateProperties) {
context.addBeanSerializerModifier(object : ValueSerializerModifier() {
override fun changeProperties(
config: SerializationConfig,
beanDesc: BeanDescription.Supplier,
beanProperties: MutableList
): MutableList {
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 (#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

Beitragsleitfaden

Für dieses Repository ist kein Beitragsleitfaden indexiert

Bewertung

Dieses Issue wurde noch nicht bewertet.

Neue Issues direkt in Ihr Postfach

Eine kurze Übersicht über anfängerfreundliche GitHub-Issues.