Skip to content

Commit b523bb2

Browse files
authored
Merge pull request #686 from k163377/feat/kotlin-name
Add UseKotlinPropertyNameForGetter option
2 parents 0860546 + aa7217f commit b523bb2

File tree

4 files changed

+112
-25
lines changed

4 files changed

+112
-25
lines changed

src/main/kotlin/com/fasterxml/jackson/module/kotlin/KotlinFeature.kt

+18-2
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
package com.fasterxml.jackson.module.kotlin
22

33
import java.util.BitSet
4-
import kotlin.math.pow
54

65
/**
76
* @see KotlinModule.Builder
@@ -42,7 +41,24 @@ enum class KotlinFeature(private val enabledByDefault: Boolean) {
4241
* may contain null values after deserialization.
4342
* Enabling it protects against this but has significant performance impact.
4443
*/
45-
StrictNullChecks(enabledByDefault = false);
44+
StrictNullChecks(enabledByDefault = false),
45+
46+
/**
47+
* By enabling this feature, the property name on Kotlin is used as the implicit name for the getter.
48+
*
49+
* By default, the getter name is used during serialization.
50+
* This name may be different from the parameter/field name, in which case serialization results
51+
* may be incorrect or annotations may malfunction.
52+
* See [jackson-module-kotlin#630] for details.
53+
*
54+
* By enabling this feature, such malfunctions will not occur.
55+
*
56+
* On the other hand, enabling this option increases the amount of reflection processing,
57+
* which may result in performance degradation for both serialization and deserialization.
58+
* In addition, the adjustment of behavior using get:JvmName is disabled.
59+
* Note also that this feature does not apply to setters.
60+
*/
61+
KotlinPropertyNameAsImplicitName(enabledByDefault = false);
4662

4763
internal val bitSet: BitSet = (1 shl ordinal).toBitSet()
4864

src/main/kotlin/com/fasterxml/jackson/module/kotlin/KotlinModule.kt

+12-3
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import com.fasterxml.jackson.module.kotlin.KotlinFeature.NullIsSameAsDefault
77
import com.fasterxml.jackson.module.kotlin.KotlinFeature.NullToEmptyCollection
88
import com.fasterxml.jackson.module.kotlin.KotlinFeature.NullToEmptyMap
99
import com.fasterxml.jackson.module.kotlin.KotlinFeature.StrictNullChecks
10+
import com.fasterxml.jackson.module.kotlin.KotlinFeature.KotlinPropertyNameAsImplicitName
1011
import com.fasterxml.jackson.module.kotlin.SingletonSupport.CANONICALIZE
1112
import com.fasterxml.jackson.module.kotlin.SingletonSupport.DISABLED
1213
import java.util.*
@@ -53,7 +54,8 @@ class KotlinModule @Deprecated(
5354
val nullToEmptyMap: Boolean = false,
5455
val nullIsSameAsDefault: Boolean = false,
5556
val singletonSupport: SingletonSupport = DISABLED,
56-
val strictNullChecks: Boolean = false
57+
val strictNullChecks: Boolean = false,
58+
val useKotlinPropertyNameForGetter: Boolean = false
5759
) : SimpleModule(KotlinModule::class.java.name, PackageVersion.VERSION) {
5860
init {
5961
if (!KotlinVersion.CURRENT.isAtLeast(1, 5)) {
@@ -102,7 +104,8 @@ class KotlinModule @Deprecated(
102104
builder.isEnabled(KotlinFeature.SingletonSupport) -> CANONICALIZE
103105
else -> DISABLED
104106
},
105-
builder.isEnabled(StrictNullChecks)
107+
builder.isEnabled(StrictNullChecks),
108+
builder.isEnabled(KotlinPropertyNameAsImplicitName)
106109
)
107110

108111
companion object {
@@ -130,7 +133,13 @@ class KotlinModule @Deprecated(
130133
}
131134

132135
context.insertAnnotationIntrospector(KotlinAnnotationIntrospector(context, cache, nullToEmptyCollection, nullToEmptyMap, nullIsSameAsDefault))
133-
context.appendAnnotationIntrospector(KotlinNamesAnnotationIntrospector(this, cache, ignoredClassesForImplyingJsonCreator))
136+
context.appendAnnotationIntrospector(
137+
KotlinNamesAnnotationIntrospector(
138+
this,
139+
cache,
140+
ignoredClassesForImplyingJsonCreator,
141+
useKotlinPropertyNameForGetter)
142+
)
134143

135144
context.addDeserializers(KotlinDeserializers())
136145
context.addKeyDeserializers(KotlinKeyDeserializers)

src/main/kotlin/com/fasterxml/jackson/module/kotlin/KotlinNamesAnnotationIntrospector.kt

+42-20
Original file line numberDiff line numberDiff line change
@@ -24,35 +24,57 @@ import kotlin.reflect.full.hasAnnotation
2424
import kotlin.reflect.full.memberProperties
2525
import kotlin.reflect.full.primaryConstructor
2626
import kotlin.reflect.jvm.internal.KotlinReflectionInternalError
27+
import kotlin.reflect.jvm.javaGetter
2728
import kotlin.reflect.jvm.javaType
2829
import kotlin.reflect.jvm.kotlinFunction
2930

30-
internal class KotlinNamesAnnotationIntrospector(val module: KotlinModule, val cache: ReflectionCache, val ignoredClassesForImplyingJsonCreator: Set<KClass<*>>) : NopAnnotationIntrospector() {
31+
internal class KotlinNamesAnnotationIntrospector(
32+
val module: KotlinModule,
33+
val cache: ReflectionCache,
34+
val ignoredClassesForImplyingJsonCreator: Set<KClass<*>>,
35+
val useKotlinPropertyNameForGetter: Boolean
36+
) : NopAnnotationIntrospector() {
37+
private fun getterNameFromJava(member: AnnotatedMethod): String? {
38+
val name = member.name
39+
40+
// The reason for truncating after `-` is to truncate the random suffix
41+
// given after the value class accessor name.
42+
return when {
43+
name.startsWith("get") -> name.takeIf { it.contains("-") }?.let { _ ->
44+
name.substringAfter("get")
45+
.replaceFirstChar { it.lowercase(Locale.getDefault()) }
46+
.substringBefore('-')
47+
}
48+
// since 2.15: support Kotlin's way of handling "isXxx" backed properties where
49+
// logical property name needs to remain "isXxx" and not become "xxx" as with Java Beans
50+
// (see https://kotlinlang.org/docs/reference/java-to-kotlin-interop.html and
51+
// https://github.com/FasterXML/jackson-databind/issues/2527 and
52+
// https://github.com/FasterXML/jackson-module-kotlin/issues/340
53+
// for details)
54+
name.startsWith("is") -> if (name.contains("-")) name.substringAfter("-") else name
55+
else -> null
56+
}
57+
}
58+
59+
private fun getterNameFromKotlin(member: AnnotatedMethod): String? {
60+
val getter = member.member
61+
62+
return member.member.declaringClass.takeIf { it.isKotlinClass() }?.let { clazz ->
63+
clazz.kotlin.memberProperties.find { it.javaGetter == getter }
64+
?.let { it.name }
65+
}
66+
}
67+
3168
// since 2.4
3269
override fun findImplicitPropertyName(member: AnnotatedMember): String? {
3370
if (!member.declaringClass.isKotlinClass()) return null
3471

35-
val name = member.name
36-
3772
return when (member) {
3873
is AnnotatedMethod -> if (member.parameterCount == 0) {
39-
// The reason for truncating after `-` is to truncate the random suffix
40-
// given after the value class accessor name.
41-
when {
42-
name.startsWith("get") -> name.takeIf { it.contains("-") }?.let { _ ->
43-
name.substringAfter("get")
44-
.replaceFirstChar { it.lowercase(Locale.getDefault()) }
45-
.substringBefore('-')
46-
}
47-
// since 2.15: support Kotlin's way of handling "isXxx" backed properties where
48-
// logical property name needs to remain "isXxx" and not become "xxx" as with Java Beans
49-
// (see https://kotlinlang.org/docs/reference/java-to-kotlin-interop.html and
50-
// https://github.com/FasterXML/jackson-databind/issues/2527 and
51-
// https://github.com/FasterXML/jackson-module-kotlin/issues/340
52-
// for details)
53-
name.startsWith("is") -> if (name.contains("-")) name.substringAfter("-") else name
54-
else -> null
55-
}
74+
if (useKotlinPropertyNameForGetter) {
75+
// Fall back to default if it is a getter-like function
76+
getterNameFromKotlin(member) ?: getterNameFromJava(member)
77+
} else getterNameFromJava(member)
5678
} else null
5779
is AnnotatedParameter -> findKotlinParameterName(member)
5880
else -> null
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
package com.fasterxml.jackson.module.kotlin.test.github
2+
3+
import com.fasterxml.jackson.annotation.JsonProperty
4+
import com.fasterxml.jackson.databind.ObjectMapper
5+
import com.fasterxml.jackson.module.kotlin.KotlinFeature
6+
import com.fasterxml.jackson.module.kotlin.KotlinModule
7+
import org.junit.Test
8+
import kotlin.test.assertEquals
9+
10+
class Github630 {
11+
private val mapper = ObjectMapper()
12+
.registerModule(KotlinModule.Builder().enable(KotlinFeature.KotlinPropertyNameAsImplicitName).build())!!
13+
14+
data class Dto(
15+
// from #570, #603
16+
val FOO: Int = 0,
17+
val bAr: Int = 0,
18+
@JsonProperty("b")
19+
val BAZ: Int = 0,
20+
@JsonProperty("q")
21+
val qUx: Int = 0,
22+
// from #71
23+
internal val quux: Int = 0,
24+
// from #434
25+
val `corge-corge`: Int = 0,
26+
// additional
27+
@get:JvmName("aaa")
28+
val grault: Int = 0
29+
)
30+
31+
@Test
32+
fun test() {
33+
val dto = Dto()
34+
35+
assertEquals(
36+
"""{"FOO":0,"bAr":0,"b":0,"q":0,"quux":0,"corge-corge":0,"grault":0}""",
37+
mapper.writeValueAsString(dto)
38+
)
39+
}
40+
}

0 commit comments

Comments
 (0)