@@ -3,15 +3,24 @@ package com.fasterxml.jackson.module.kotlin
3
3
import com.fasterxml.jackson.databind.BeanDescription
4
4
import com.fasterxml.jackson.databind.DeserializationConfig
5
5
import com.fasterxml.jackson.databind.DeserializationContext
6
+ import com.fasterxml.jackson.databind.MapperFeature
6
7
import com.fasterxml.jackson.databind.deser.SettableBeanProperty
7
8
import com.fasterxml.jackson.databind.deser.ValueInstantiator
8
9
import com.fasterxml.jackson.databind.deser.ValueInstantiators
9
10
import com.fasterxml.jackson.databind.deser.impl.NullsAsEmptyProvider
10
11
import com.fasterxml.jackson.databind.deser.impl.PropertyValueBuffer
11
12
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
12
17
import java.lang.reflect.TypeVariable
13
18
import kotlin.reflect.KParameter
14
19
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
15
24
import kotlin.reflect.jvm.javaType
16
25
17
26
internal class KotlinValueInstantiator (
@@ -24,8 +33,7 @@ internal class KotlinValueInstantiator(
24
33
private val strictNullChecks : Boolean ,
25
34
private val experimentalDeserializationBackend : Boolean
26
35
) : StdValueInstantiator(src) {
27
- @Suppress(" UNCHECKED_CAST" )
28
- override fun createFromObjectWith (
36
+ private fun experimentalCreateFromObjectWith (
29
37
ctxt : DeserializationContext ,
30
38
props : Array <out SettableBeanProperty >,
31
39
buffer : PropertyValueBuffer
@@ -109,6 +117,157 @@ internal class KotlinValueInstantiator(
109
117
}
110
118
}
111
119
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
+
112
271
private fun KParameter.isPrimitive (): Boolean {
113
272
return when (val javaType = type.javaType) {
114
273
is Class <* > -> javaType.isPrimitive
0 commit comments