Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Optimize and Refactor KotlinValueInstantiator.createFromObjectWith #687

Merged
merged 4 commits into from
Jul 15, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions release-notes/CREDITS-2.x
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ Contributors:
# 2.16.0 (not yet released)

WrongWrong (@k163377)
* #687: Optimize and Refactor KotlinValueInstantiator.createFromObjectWith
* #685: Streamline default value management for KotlinFeatures
* #684: Update Kotlin Version to 1.6
* #682: Remove MissingKotlinParameterException and replace with MismatchedInputException
Expand Down
1 change: 1 addition & 0 deletions release-notes/VERSION-2.x
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ Co-maintainers:

2.16.0 (not yet released)

#687: Optimize and Refactor KotlinValueInstantiator.createFromObjectWith.
#685: Streamline default value management for KotlinFeatures.
This improves the initialization cost of kotlin-module a little.
#684: Kotlin 1.5 has been deprecated and the minimum supported Kotlin version will be updated to 1.6.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package com.fasterxml.jackson.module.kotlin
import com.fasterxml.jackson.databind.BeanDescription
import com.fasterxml.jackson.databind.DeserializationConfig
import com.fasterxml.jackson.databind.DeserializationContext
import com.fasterxml.jackson.databind.JavaType
import com.fasterxml.jackson.databind.deser.SettableBeanProperty
import com.fasterxml.jackson.databind.deser.ValueInstantiator
import com.fasterxml.jackson.databind.deser.ValueInstantiators
Expand All @@ -13,6 +14,7 @@ import com.fasterxml.jackson.databind.exc.MismatchedInputException
import java.lang.reflect.TypeVariable
import kotlin.reflect.KParameter
import kotlin.reflect.KType
import kotlin.reflect.KTypeProjection
import kotlin.reflect.jvm.javaType

internal class KotlinValueInstantiator(
Expand All @@ -23,6 +25,13 @@ internal class KotlinValueInstantiator(
private val nullIsSameAsDefault: Boolean,
private val strictNullChecks: Boolean
) : StdValueInstantiator(src) {
private fun JavaType.requireEmptyValue() =
(nullToEmptyCollection && this.isCollectionLikeType) || (nullToEmptyMap && this.isMapLikeType)

private fun KType.isGenericTypeVar() = javaType is TypeVariable<*>

private fun List<KTypeProjection>.markedNonNullAt(index: Int) = getOrNull(index)?.type?.isMarkedNullable == false

override fun createFromObjectWith(
ctxt: DeserializationContext,
props: Array<out SettableBeanProperty>,
Expand Down Expand Up @@ -58,14 +67,15 @@ internal class KotlinValueInstantiator(
return@forEachIndexed
}

val paramType = paramDef.type
var paramVal = if (!isMissing || paramDef.isPrimitive() || jsonProp.hasInjectableValueId()) {
val tempParamVal = buffer.getParameter(jsonProp)
if (nullIsSameAsDefault && tempParamVal == null && paramDef.isOptional) {
return@forEachIndexed
}
tempParamVal
} else {
if(paramDef.type.isMarkedNullable) {
if(paramType.isMarkedNullable) {
// do not try to create any object if it is nullable and the value is missing
null
} else {
Expand All @@ -74,46 +84,51 @@ internal class KotlinValueInstantiator(
}
}

if (paramVal == null && ((nullToEmptyCollection && jsonProp.type.isCollectionLikeType) || (nullToEmptyMap && jsonProp.type.isMapLikeType))) {
paramVal = NullsAsEmptyProvider(jsonProp.valueDeserializer).getNullValue(ctxt)
}
val propType = jsonProp.type

val isGenericTypeVar = paramDef.type.javaType is TypeVariable<*>
val isMissingAndRequired = paramVal == null && isMissing && jsonProp.isRequired
if (isMissingAndRequired ||
(!isGenericTypeVar && paramVal == null && !paramDef.type.isMarkedNullable)) {
throw MismatchedInputException.from(
ctxt.parser,
jsonProp.type,
"Instantiation of $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"
).wrapWithPath(this.valueClass, jsonProp.name)
}
if (paramVal == null) {
if (propType.requireEmptyValue()) {
paramVal = NullsAsEmptyProvider(jsonProp.valueDeserializer).getNullValue(ctxt)
} else {
val isMissingAndRequired = isMissing && jsonProp.isRequired

// Since #310 reported that the calculation cost is high, isGenericTypeVar is determined last.
if (isMissingAndRequired || (!paramType.isMarkedNullable && !paramType.isGenericTypeVar())) {
throw MismatchedInputException.from(
ctxt.parser,
propType,
"Instantiation of $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"
).wrapWithPath(this.valueClass, jsonProp.name)
}
}
} else if (strictNullChecks) {
val arguments = paramType.arguments

if (strictNullChecks && paramVal != null) {
var paramType: String? = null
var paramTypeStr: String? = null
var itemType: KType? = null
if (jsonProp.type.isCollectionLikeType && paramDef.type.arguments.getOrNull(0)?.type?.isMarkedNullable == false && (paramVal as Collection<*>).any { it == null }) {
paramType = "collection"
itemType = paramDef.type.arguments[0].type

if (propType.isCollectionLikeType && arguments.markedNonNullAt(0) && (paramVal as Collection<*>).any { it == null }) {
paramTypeStr = "collection"
itemType = arguments[0].type
}

if (jsonProp.type.isMapLikeType && paramDef.type.arguments.getOrNull(1)?.type?.isMarkedNullable == false && (paramVal as Map<*, *>).any { it.value == null }) {
paramType = "map"
itemType = paramDef.type.arguments[1].type
if (propType.isMapLikeType && arguments.markedNonNullAt(1) && (paramVal as Map<*, *>).any { it.value == null }) {
paramTypeStr = "map"
itemType = arguments[1].type
}

if (jsonProp.type.isArrayType && paramDef.type.arguments.getOrNull(0)?.type?.isMarkedNullable == false && (paramVal as Array<*>).any { it == null }) {
paramType = "array"
itemType = paramDef.type.arguments[0].type
if (propType.isArrayType && arguments.markedNonNullAt(0) && (paramVal as Array<*>).any { it == null }) {
paramTypeStr = "array"
itemType = arguments[0].type
}

if (paramType != null && itemType != null) {
if (paramTypeStr != null && itemType != null) {
throw MismatchedInputException.from(
ctxt.parser,
jsonProp.type,
"Instantiation of $itemType $paramType failed for JSON property ${jsonProp.name} due to null value in a $paramType that does not allow null values"
propType,
"Instantiation of $itemType $paramTypeStr failed for JSON property ${jsonProp.name} due to null value in a $paramTypeStr that does not allow null values"
).wrapWithPath(this.valueClass, jsonProp.name)
}
}
Expand Down