Skip to content

Commit 517f534

Browse files
committed
Changed the priority of the requirement identification process to be lower than JacksonAnnotationIntrospector
closes #941
1 parent 6825843 commit 517f534

File tree

3 files changed

+109
-113
lines changed

3 files changed

+109
-113
lines changed
Original file line numberDiff line numberDiff line change
@@ -1,63 +1,19 @@
11
package tools.jackson.module.kotlin
22

3-
import tools.jackson.databind.DeserializationFeature
4-
import tools.jackson.databind.JacksonModule
53
import tools.jackson.databind.cfg.MapperConfig
64
import tools.jackson.databind.introspect.Annotated
75
import tools.jackson.databind.introspect.AnnotatedClass
8-
import tools.jackson.databind.introspect.AnnotatedField
9-
import tools.jackson.databind.introspect.AnnotatedMember
106
import tools.jackson.databind.introspect.AnnotatedMethod
11-
import tools.jackson.databind.introspect.AnnotatedParameter
127
import tools.jackson.databind.introspect.NopAnnotationIntrospector
138
import tools.jackson.databind.jsontype.NamedType
149
import tools.jackson.databind.util.Converter
15-
import java.lang.reflect.Field
16-
import java.lang.reflect.Method
17-
import kotlin.reflect.KFunction
18-
import kotlin.reflect.KMutableProperty1
19-
import kotlin.reflect.KParameter
20-
import kotlin.reflect.KProperty1
21-
import kotlin.reflect.KType
22-
import kotlin.reflect.full.createType
23-
import kotlin.reflect.full.declaredMemberProperties
24-
import kotlin.reflect.full.valueParameters
25-
import kotlin.reflect.jvm.javaGetter
26-
import kotlin.reflect.jvm.javaSetter
27-
import kotlin.reflect.jvm.javaType
28-
import kotlin.reflect.jvm.kotlinProperty
2910
import kotlin.time.Duration
3011

3112
internal class KotlinAnnotationIntrospector(
32-
private val context: JacksonModule.SetupContext,
3313
private val cache: ReflectionCache,
34-
private val nullToEmptyCollection: Boolean,
35-
private val nullToEmptyMap: Boolean,
36-
private val nullIsSameAsDefault: Boolean,
3714
private val useJavaDurationConversion: Boolean,
3815
) : NopAnnotationIntrospector() {
3916

40-
// TODO: implement nullIsSameAsDefault flag, which represents when TRUE that if something has a default value, it can be passed a null to default it
41-
// this likely impacts this class to be accurate about what COULD be considered required
42-
43-
override fun hasRequiredMarker(
44-
cfg: MapperConfig<*>,
45-
m: AnnotatedMember
46-
): Boolean? = m.takeIf { it.member.declaringClass.isKotlinClass() }?.let { _ ->
47-
cache.javaMemberIsRequired(m) {
48-
try {
49-
when (m) {
50-
is AnnotatedField -> m.hasRequiredMarker()
51-
is AnnotatedMethod -> m.hasRequiredMarker()
52-
is AnnotatedParameter -> m.hasRequiredMarker()
53-
else -> null
54-
}
55-
} catch (_: UnsupportedOperationException) {
56-
null
57-
}
58-
}
59-
}
60-
6117
override fun findSerializationConverter(config: MapperConfig<*>?, a: Annotated): Converter<*, *>? = when (a) {
6218
// Find a converter to handle the case where the getter returns an unboxed value from the value class.
6319
is AnnotatedMethod -> a.findValueClassReturnType()?.let {
@@ -100,65 +56,5 @@ internal class KotlinAnnotationIntrospector(
10056
.ifEmpty { null }
10157
}
10258

103-
private fun AnnotatedField.hasRequiredMarker(): Boolean? {
104-
val field = member as Field
105-
return field.kotlinProperty?.returnType?.isRequired()
106-
}
107-
108-
// Since Kotlin's property has the same Type for each field, getter, and setter,
109-
// nullability can be determined from the returnType of KProperty.
110-
private fun KProperty1<*, *>.isRequiredByNullability() = returnType.isRequired()
111-
112-
// This could be a setter or a getter of a class property or
113-
// a setter-like/getter-like method.
114-
private fun AnnotatedMethod.hasRequiredMarker(): Boolean? = this.getRequiredMarkerFromCorrespondingAccessor()
115-
?: this.member.getRequiredMarkerFromAccessorLikeMethod()
116-
117-
private fun AnnotatedMethod.getRequiredMarkerFromCorrespondingAccessor(): Boolean? {
118-
member.declaringClass.kotlin.declaredMemberProperties.forEach { kProperty ->
119-
if (kProperty.javaGetter == this.member || (kProperty as? KMutableProperty1)?.javaSetter == this.member) {
120-
return kProperty.isRequiredByNullability()
121-
}
122-
}
123-
return null
124-
}
125-
126-
// Is the member method a regular method of the data class or
127-
private fun Method.getRequiredMarkerFromAccessorLikeMethod(): Boolean? = cache.kotlinFromJava(this)?.let { func ->
128-
when {
129-
func.isGetterLike() -> func.returnType.isRequired()
130-
// If nullToEmpty could be supported for setters,
131-
// a branch similar to AnnotatedParameter.hasRequiredMarker should be added.
132-
func.isSetterLike() -> func.valueParameters[0].isRequired()
133-
else -> null
134-
}
135-
}
136-
137-
private fun KFunction<*>.isGetterLike(): Boolean = parameters.size == 1
138-
private fun KFunction<*>.isSetterLike(): Boolean = parameters.size == 2 && returnType == UNIT_TYPE
139-
140-
private fun AnnotatedParameter.hasRequiredMarker(): Boolean? = when {
141-
nullToEmptyCollection && type.isCollectionLikeType -> false
142-
nullToEmptyMap && type.isMapLikeType -> false
143-
else -> cache.findKotlinParameter(this)?.isRequired()
144-
}
145-
14659
private fun AnnotatedMethod.findValueClassReturnType() = cache.findValueClassReturnType(this)
147-
148-
private fun KParameter.isRequired(): Boolean {
149-
val paramType = type
150-
val isPrimitive = when (val javaType = paramType.javaType) {
151-
is Class<*> -> javaType.isPrimitive
152-
else -> false
153-
}
154-
155-
return !paramType.isMarkedNullable && !isOptional && !isVararg &&
156-
!(isPrimitive && !context.isEnabled(DeserializationFeature.FAIL_ON_NULL_FOR_PRIMITIVES))
157-
}
158-
159-
private fun KType.isRequired(): Boolean = !isMarkedNullable
160-
161-
companion object {
162-
val UNIT_TYPE: KType by lazy { Unit::class.createType() }
163-
}
16460
}

src/main/kotlin/tools/jackson/module/kotlin/KotlinModule.kt

+10-9
Original file line numberDiff line numberDiff line change
@@ -92,16 +92,17 @@ class KotlinModule private constructor(
9292
context.addDeserializerModifier(KotlinValueDeserializerModifier)
9393
}
9494

95-
context.insertAnnotationIntrospector(KotlinAnnotationIntrospector(
96-
context,
97-
cache,
98-
nullToEmptyCollection,
99-
nullToEmptyMap,
100-
nullIsSameAsDefault,
101-
useJavaDurationConversion
102-
))
95+
context.insertAnnotationIntrospector(KotlinAnnotationIntrospector(cache, useJavaDurationConversion))
10396
context.appendAnnotationIntrospector(
104-
KotlinNamesAnnotationIntrospector(cache, newStrictNullChecks, kotlinPropertyNameAsImplicitName)
97+
KotlinNamesAnnotationIntrospector(
98+
context = context,
99+
cache = cache,
100+
nullToEmptyCollection = nullToEmptyCollection,
101+
nullToEmptyMap = nullToEmptyMap,
102+
nullIsSameAsDefault = nullIsSameAsDefault,
103+
strictNullChecks = newStrictNullChecks,
104+
kotlinPropertyNameAsImplicitName = kotlinPropertyNameAsImplicitName
105+
)
105106
)
106107

107108
context.addDeserializers(KotlinDeserializers(cache, useJavaDurationConversion))

src/main/kotlin/tools/jackson/module/kotlin/KotlinNamesAnnotationIntrospector.kt

+99
Original file line numberDiff line numberDiff line change
@@ -3,32 +3,127 @@ package tools.jackson.module.kotlin
33
import com.fasterxml.jackson.annotation.JsonProperty
44
import com.fasterxml.jackson.annotation.JsonSetter
55
import com.fasterxml.jackson.annotation.Nulls
6+
import tools.jackson.databind.DeserializationFeature
7+
import tools.jackson.databind.JacksonModule
68
import tools.jackson.databind.JavaType
79
import tools.jackson.databind.cfg.MapperConfig
810
import tools.jackson.databind.introspect.Annotated
911
import tools.jackson.databind.introspect.AnnotatedClass
12+
import tools.jackson.databind.introspect.AnnotatedField
1013
import tools.jackson.databind.introspect.AnnotatedMember
1114
import tools.jackson.databind.introspect.AnnotatedMethod
1215
import tools.jackson.databind.introspect.AnnotatedParameter
1316
import tools.jackson.databind.introspect.NopAnnotationIntrospector
1417
import tools.jackson.databind.introspect.PotentialCreator
1518
import java.lang.reflect.Constructor
19+
import java.lang.reflect.Field
20+
import java.lang.reflect.Method
1621
import java.util.Locale
1722
import kotlin.collections.getOrNull
1823
import kotlin.reflect.KClass
1924
import kotlin.reflect.KFunction
25+
import kotlin.reflect.KMutableProperty1
2026
import kotlin.reflect.KParameter
27+
import kotlin.reflect.KProperty1
28+
import kotlin.reflect.KType
29+
import kotlin.reflect.full.createType
30+
import kotlin.reflect.full.declaredMemberProperties
2131
import kotlin.reflect.full.hasAnnotation
2232
import kotlin.reflect.full.memberProperties
2333
import kotlin.reflect.full.primaryConstructor
34+
import kotlin.reflect.full.valueParameters
2435
import kotlin.reflect.jvm.javaGetter
36+
import kotlin.reflect.jvm.javaSetter
2537
import kotlin.reflect.jvm.javaType
38+
import kotlin.reflect.jvm.kotlinProperty
2639

2740
internal class KotlinNamesAnnotationIntrospector(
41+
private val context: JacksonModule.SetupContext,
2842
private val cache: ReflectionCache,
43+
private val nullToEmptyCollection: Boolean,
44+
private val nullToEmptyMap: Boolean,
45+
private val nullIsSameAsDefault: Boolean,
2946
private val strictNullChecks: Boolean,
3047
private val kotlinPropertyNameAsImplicitName: Boolean
3148
) : NopAnnotationIntrospector() {
49+
private fun KType.isRequired(): Boolean = !isMarkedNullable
50+
51+
// Since Kotlin's property has the same Type for each field, getter, and setter,
52+
// nullability can be determined from the returnType of KProperty.
53+
private fun KProperty1<*, *>.isRequiredByNullability() = returnType.isRequired()
54+
55+
private fun KParameter.isRequired(): Boolean {
56+
val paramType = type
57+
val isPrimitive = when (val javaType = paramType.javaType) {
58+
is Class<*> -> javaType.isPrimitive
59+
else -> false
60+
}
61+
62+
return !paramType.isMarkedNullable && !isOptional && !isVararg &&
63+
!(isPrimitive && !context.isEnabled(DeserializationFeature.FAIL_ON_NULL_FOR_PRIMITIVES))
64+
}
65+
66+
private fun AnnotatedField.hasRequiredMarker(): Boolean? {
67+
val field = member as Field
68+
return field.kotlinProperty?.returnType?.isRequired()
69+
}
70+
71+
private fun KFunction<*>.isGetterLike(): Boolean = parameters.size == 1
72+
private fun KFunction<*>.isSetterLike(): Boolean = parameters.size == 2 && returnType == UNIT_TYPE
73+
74+
private fun AnnotatedMethod.getRequiredMarkerFromCorrespondingAccessor(): Boolean? {
75+
member.declaringClass.kotlin.declaredMemberProperties.forEach { kProperty ->
76+
if (kProperty.javaGetter == this.member || (kProperty as? KMutableProperty1)?.javaSetter == this.member) {
77+
return kProperty.isRequiredByNullability()
78+
}
79+
}
80+
return null
81+
}
82+
83+
// Is the member method a regular method of the data class or
84+
private fun Method.getRequiredMarkerFromAccessorLikeMethod(): Boolean? = cache.kotlinFromJava(this)?.let { func ->
85+
when {
86+
func.isGetterLike() -> func.returnType.isRequired()
87+
// If nullToEmpty could be supported for setters,
88+
// a branch similar to AnnotatedParameter.hasRequiredMarker should be added.
89+
func.isSetterLike() -> func.valueParameters[0].isRequired()
90+
else -> null
91+
}
92+
}
93+
94+
// This could be a setter or a getter of a class property or
95+
// a setter-like/getter-like method.
96+
private fun AnnotatedMethod.hasRequiredMarker(): Boolean? = this.getRequiredMarkerFromCorrespondingAccessor()
97+
?: this.member.getRequiredMarkerFromAccessorLikeMethod()
98+
99+
// TODO: implement nullIsSameAsDefault flag, which represents when TRUE that if something has a default value, it can be passed a null to default it
100+
// this likely impacts this class to be accurate about what COULD be considered required
101+
private fun AnnotatedParameter.hasRequiredMarker(): Boolean? = when {
102+
nullToEmptyCollection && type.isCollectionLikeType -> false
103+
nullToEmptyMap && type.isMapLikeType -> false
104+
else -> cache.findKotlinParameter(this)?.isRequired()
105+
}
106+
107+
override fun hasRequiredMarker(
108+
cfg: MapperConfig<*>,
109+
m: AnnotatedMember
110+
): Boolean? = m.takeIf { it.member.declaringClass.isKotlinClass() }?.let { _ ->
111+
println(m)
112+
113+
cache.javaMemberIsRequired(m) {
114+
try {
115+
when (m) {
116+
is AnnotatedField -> m.hasRequiredMarker()
117+
is AnnotatedMethod -> m.hasRequiredMarker()
118+
is AnnotatedParameter -> m.hasRequiredMarker()
119+
else -> null
120+
}
121+
} catch (_: UnsupportedOperationException) {
122+
null
123+
}
124+
}
125+
}
126+
32127
private fun getterNameFromJava(member: AnnotatedMethod): String? {
33128
val name = member.name
34129

@@ -123,6 +218,10 @@ internal class KotlinNamesAnnotationIntrospector(
123218

124219
private fun findKotlinParameter(param: Annotated) = (param as? AnnotatedParameter)
125220
?.let { cache.findKotlinParameter(it) }
221+
222+
companion object {
223+
val UNIT_TYPE: KType by lazy { Unit::class.createType() }
224+
}
126225
}
127226

128227
private fun KParameter.markedNonNullAt(index: Int) = type.arguments.getOrNull(index)?.type?.isMarkedNullable == false

0 commit comments

Comments
 (0)