Skip to content

Commit 75a1c3e

Browse files
committed
add experimentalDeserializationBackend switch
1 parent 2bc2743 commit 75a1c3e

File tree

1 file changed

+161
-2
lines changed

1 file changed

+161
-2
lines changed

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

+161-2
Original file line numberDiff line numberDiff line change
@@ -3,15 +3,24 @@ package com.fasterxml.jackson.module.kotlin
33
import com.fasterxml.jackson.databind.BeanDescription
44
import com.fasterxml.jackson.databind.DeserializationConfig
55
import com.fasterxml.jackson.databind.DeserializationContext
6+
import com.fasterxml.jackson.databind.MapperFeature
67
import com.fasterxml.jackson.databind.deser.SettableBeanProperty
78
import com.fasterxml.jackson.databind.deser.ValueInstantiator
89
import com.fasterxml.jackson.databind.deser.ValueInstantiators
910
import com.fasterxml.jackson.databind.deser.impl.NullsAsEmptyProvider
1011
import com.fasterxml.jackson.databind.deser.impl.PropertyValueBuffer
1112
import com.fasterxml.jackson.databind.deser.std.StdValueInstantiator
13+
import com.fasterxml.jackson.databind.introspect.AnnotatedConstructor
14+
import com.fasterxml.jackson.databind.introspect.AnnotatedMethod
15+
import java.lang.reflect.Constructor
16+
import java.lang.reflect.Method
1217
import java.lang.reflect.TypeVariable
1318
import kotlin.reflect.KParameter
1419
import kotlin.reflect.KType
20+
import kotlin.reflect.full.extensionReceiverParameter
21+
import kotlin.reflect.full.instanceParameter
22+
import kotlin.reflect.full.valueParameters
23+
import kotlin.reflect.jvm.isAccessible
1524
import kotlin.reflect.jvm.javaType
1625

1726
internal class KotlinValueInstantiator(
@@ -24,8 +33,7 @@ internal class KotlinValueInstantiator(
2433
private val strictNullChecks: Boolean,
2534
private val experimentalDeserializationBackend: Boolean
2635
) : StdValueInstantiator(src) {
27-
@Suppress("UNCHECKED_CAST")
28-
override fun createFromObjectWith(
36+
private fun experimentalCreateFromObjectWith(
2937
ctxt: DeserializationContext,
3038
props: Array<out SettableBeanProperty>,
3139
buffer: PropertyValueBuffer
@@ -109,6 +117,157 @@ internal class KotlinValueInstantiator(
109117
}
110118
}
111119

120+
@Suppress("UNCHECKED_CAST")
121+
private fun conventionalCreateFromObjectWith(
122+
ctxt: DeserializationContext,
123+
props: Array<out SettableBeanProperty>,
124+
buffer: PropertyValueBuffer
125+
): Any? {
126+
val callable = when (_withArgsCreator) {
127+
is AnnotatedConstructor -> cache.kotlinFromJava(_withArgsCreator.annotated as Constructor<Any>)
128+
is AnnotatedMethod -> cache.kotlinFromJava(_withArgsCreator.annotated as Method)
129+
else -> throw IllegalStateException("Expected a constructor or method to create a Kotlin object, instead found ${_withArgsCreator.annotated.javaClass.name}")
130+
} ?: return super.createFromObjectWith(
131+
ctxt,
132+
props,
133+
buffer
134+
) // we cannot reflect this method so do the default Java-ish behavior
135+
136+
if (callable.extensionReceiverParameter != null) {
137+
// we shouldn't have an instance or receiver parameter and if we do, just go with default Java-ish behavior
138+
return super.createFromObjectWith(ctxt, props, buffer)
139+
}
140+
141+
val propCount = props.size + if (callable.instanceParameter != null) 1 else 0
142+
143+
var numCallableParameters = 0
144+
val callableParameters = arrayOfNulls<KParameter>(propCount)
145+
val jsonParamValueList = arrayOfNulls<Any>(propCount)
146+
147+
if (callable.instanceParameter != null) {
148+
val possibleCompanion = callable.instanceParameter!!.type.erasedType().kotlin
149+
150+
if (!possibleCompanion.isCompanion) {
151+
// abort, we have some unknown case here
152+
return super.createFromObjectWith(ctxt, props, buffer)
153+
}
154+
155+
// TODO: cache this lookup since the exception throwing/catching can be expensive
156+
jsonParamValueList[numCallableParameters] = try {
157+
possibleCompanion.objectInstance
158+
} catch (ex: IllegalAccessException) {
159+
// fallback for when an odd access exception happens through Kotlin reflection
160+
val companionField = possibleCompanion.java.enclosingClass.fields.firstOrNull { it.name == "Companion" }
161+
?: throw ex
162+
val accessible = companionField.isAccessible
163+
if ((!accessible && ctxt.config.isEnabled(MapperFeature.CAN_OVERRIDE_ACCESS_MODIFIERS)) ||
164+
(accessible && ctxt.config.isEnabled(MapperFeature.OVERRIDE_PUBLIC_ACCESS_MODIFIERS))
165+
) {
166+
companionField.isAccessible = true
167+
}
168+
companionField.get(null) ?: throw ex
169+
}
170+
171+
callableParameters[numCallableParameters] = callable.instanceParameter
172+
numCallableParameters++
173+
}
174+
175+
callable.valueParameters.forEachIndexed { idx, paramDef ->
176+
val jsonProp = props[idx]
177+
val isMissing = !buffer.hasParameter(jsonProp)
178+
179+
if (isMissing && paramDef.isOptional) {
180+
return@forEachIndexed
181+
}
182+
183+
var paramVal = if (!isMissing || paramDef.isPrimitive() || jsonProp.hasInjectableValueId()) {
184+
val tempParamVal = buffer.getParameter(jsonProp)
185+
if (nullIsSameAsDefault && tempParamVal == null && paramDef.isOptional) {
186+
return@forEachIndexed
187+
}
188+
tempParamVal
189+
} else {
190+
// trying to get suitable "missing" value provided by deserializer
191+
jsonProp.valueDeserializer?.getNullValue(ctxt)
192+
}
193+
194+
if (paramVal == null && ((nullToEmptyCollection && jsonProp.type.isCollectionLikeType) || (nullToEmptyMap && jsonProp.type.isMapLikeType))) {
195+
paramVal = NullsAsEmptyProvider(jsonProp.valueDeserializer).getNullValue(ctxt)
196+
}
197+
198+
val isGenericTypeVar = paramDef.type.javaType is TypeVariable<*>
199+
val isMissingAndRequired = paramVal == null && isMissing && jsonProp.isRequired
200+
if (isMissingAndRequired ||
201+
(!isGenericTypeVar && paramVal == null && !paramDef.type.isMarkedNullable)) {
202+
throw MissingKotlinParameterException(
203+
parameter = paramDef,
204+
processor = ctxt.parser,
205+
msg = "Instantiation of ${this.valueTypeDesc} value failed for JSON property ${jsonProp.name} due to missing (therefore NULL) value for creator parameter ${paramDef.name} which is a non-nullable type"
206+
).wrapWithPath(this.valueClass, jsonProp.name)
207+
}
208+
209+
if (strictNullChecks && paramVal != null) {
210+
var paramType: String? = null
211+
var itemType: KType? = null
212+
if (jsonProp.type.isCollectionLikeType && paramDef.type.arguments.getOrNull(0)?.type?.isMarkedNullable == false && (paramVal as Collection<*>).any { it == null }) {
213+
paramType = "collection"
214+
itemType = paramDef.type.arguments[0].type
215+
}
216+
217+
if (jsonProp.type.isMapLikeType && paramDef.type.arguments.getOrNull(1)?.type?.isMarkedNullable == false && (paramVal as Map<*, *>).any { it.value == null }) {
218+
paramType = "map"
219+
itemType = paramDef.type.arguments[1].type
220+
}
221+
222+
if (jsonProp.type.isArrayType && paramDef.type.arguments.getOrNull(0)?.type?.isMarkedNullable == false && (paramVal as Array<*>).any { it == null }) {
223+
paramType = "array"
224+
itemType = paramDef.type.arguments[0].type
225+
}
226+
227+
if (paramType != null && itemType != null) {
228+
throw MissingKotlinParameterException(
229+
parameter = paramDef,
230+
processor = ctxt.parser,
231+
msg = "Instantiation of $itemType $paramType failed for JSON property ${jsonProp.name} due to null value in a $paramType that does not allow null values"
232+
).wrapWithPath(this.valueClass, jsonProp.name)
233+
}
234+
}
235+
236+
jsonParamValueList[numCallableParameters] = paramVal
237+
callableParameters[numCallableParameters] = paramDef
238+
numCallableParameters++
239+
}
240+
241+
return if (numCallableParameters == jsonParamValueList.size && callable.instanceParameter == null) {
242+
// we didn't do anything special with default parameters, do a normal call
243+
super.createFromObjectWith(ctxt, jsonParamValueList)
244+
} else {
245+
val accessible = callable.isAccessible
246+
if ((!accessible && ctxt.config.isEnabled(MapperFeature.CAN_OVERRIDE_ACCESS_MODIFIERS)) ||
247+
(accessible && ctxt.config.isEnabled(MapperFeature.OVERRIDE_PUBLIC_ACCESS_MODIFIERS))
248+
) {
249+
callable.isAccessible = true
250+
}
251+
val callableParametersByName = linkedMapOf<KParameter, Any?>()
252+
callableParameters.mapIndexed { idx, paramDef ->
253+
if (paramDef != null) {
254+
callableParametersByName[paramDef] = jsonParamValueList[idx]
255+
}
256+
}
257+
callable.callBy(callableParametersByName)
258+
}
259+
}
260+
261+
override fun createFromObjectWith(
262+
ctxt: DeserializationContext,
263+
props: Array<out SettableBeanProperty>,
264+
buffer: PropertyValueBuffer
265+
): Any? = if (experimentalDeserializationBackend) {
266+
experimentalCreateFromObjectWith(ctxt, props, buffer)
267+
} else {
268+
conventionalCreateFromObjectWith(ctxt, props, buffer)
269+
}
270+
112271
private fun KParameter.isPrimitive(): Boolean {
113272
return when (val javaType = type.javaType) {
114273
is Class<*> -> javaType.isPrimitive

0 commit comments

Comments
 (0)