diff --git a/pom.xml b/pom.xml index 7001baf4..56c802e7 100644 --- a/pom.xml +++ b/pom.xml @@ -254,6 +254,12 @@ com.fasterxml.jackson.module.kotlin.MissingKotlinParameterException + + com.fasterxml.jackson.module.kotlin.KotlinInstantiators#KotlinInstantiators(com.fasterxml.jackson.module.kotlin.ReflectionCache,boolean,boolean,boolean,boolean) + + + com.fasterxml.jackson.module.kotlin.KotlinValueInstantiator#KotlinValueInstantiator(com.fasterxml.jackson.databind.deser.std.StdValueInstantiator,com.fasterxml.jackson.module.kotlin.ReflectionCache,boolean,boolean,boolean,boolean) + diff --git a/release-notes/CREDITS-2.x b/release-notes/CREDITS-2.x index 18e27f04..80485b08 100644 --- a/release-notes/CREDITS-2.x +++ b/release-notes/CREDITS-2.x @@ -18,6 +18,7 @@ Contributors: # 2.21.0 (not yet released) WrongWrong (@k163377) +* #1042: Remove old StrictNullChecks * #1041: Remove MissingKotlinParameterException * #1039: Update settings for 2.20 diff --git a/release-notes/VERSION-2.x b/release-notes/VERSION-2.x index eaa01fe7..168a2abe 100644 --- a/release-notes/VERSION-2.x +++ b/release-notes/VERSION-2.x @@ -18,6 +18,8 @@ Co-maintainers: 2.21.0 (not yet released) +#1042: The old StrictNullChecks backend has been removed. + This improves the throughput of deserialization slightly. #1041: The deprecated MissingKotlinParameterException has been removed. #1039: Kotlin has been upgraded to 2.1.x. diff --git a/src/main/kotlin/com/fasterxml/jackson/module/kotlin/KotlinFeature.kt b/src/main/kotlin/com/fasterxml/jackson/module/kotlin/KotlinFeature.kt index 1e9a62a9..6ed9f16b 100644 --- a/src/main/kotlin/com/fasterxml/jackson/module/kotlin/KotlinFeature.kt +++ b/src/main/kotlin/com/fasterxml/jackson/module/kotlin/KotlinFeature.kt @@ -40,13 +40,12 @@ enum class KotlinFeature(internal val enabledByDefault: Boolean) { * * With this disabled, the default, collections which are typed to disallow null members (e.g. `List`) * may contain null values after deserialization. - * Enabling it protects against this but has significant performance impact. + * Enabling this will cause an [InvalidNullException] to be thrown if null is entered. + * + * Internally, it will be the same as if [JsonSetter] (contentNulls = FAIL) had been granted. + * + * Benchmarks show that it can check for illegal nulls with throughput nearly identical to the default (see [jackson-module-kotlin#719]). */ - @Deprecated( - level = DeprecationLevel.ERROR, - message = "This option will be migrated to the new backend in 2.21.", - replaceWith = ReplaceWith("NewStrictNullChecks") - ) StrictNullChecks(enabledByDefault = false), /** @@ -80,15 +79,15 @@ enum class KotlinFeature(internal val enabledByDefault: Boolean) { * Internally, it will be the same as if [JsonSetter] (contentNulls = FAIL) had been granted. * Benchmarks show that it can check for illegal nulls with throughput nearly identical to the default (see [jackson-module-kotlin#719]). * - * Note that in the new backend, the exception thrown has changed from [MissingKotlinParameterException] to [InvalidNullException]. - * The message will be changed accordingly. - * Since 2.19, the base class of [MissingKotlinParameterException] has also been changed to [InvalidNullException], - * so be careful when catching it. - * * This is a temporary option for a phased backend migration, * which will eventually be merged into [StrictNullChecks]. * Also, specifying both this and [StrictNullChecks] is not permitted. */ + @Deprecated( + level = DeprecationLevel.WARNING, + message = "This option will be merged into StrictNullChecks in 2.23.", + replaceWith = ReplaceWith("StrictNullChecks") + ) NewStrictNullChecks(enabledByDefault = false); internal val bitSet: BitSet = (1 shl ordinal).toBitSet() diff --git a/src/main/kotlin/com/fasterxml/jackson/module/kotlin/KotlinModule.kt b/src/main/kotlin/com/fasterxml/jackson/module/kotlin/KotlinModule.kt index d23afd4e..c36dca09 100644 --- a/src/main/kotlin/com/fasterxml/jackson/module/kotlin/KotlinModule.kt +++ b/src/main/kotlin/com/fasterxml/jackson/module/kotlin/KotlinModule.kt @@ -43,11 +43,10 @@ class KotlinModule private constructor( val nullToEmptyMap: Boolean = NullToEmptyMap.enabledByDefault, val nullIsSameAsDefault: Boolean = NullIsSameAsDefault.enabledByDefault, val singletonSupport: Boolean = SingletonSupport.enabledByDefault, - @Suppress("DEPRECATION_ERROR") strictNullChecks: Boolean = StrictNullChecks.enabledByDefault, val kotlinPropertyNameAsImplicitName: Boolean = KotlinPropertyNameAsImplicitName.enabledByDefault, val useJavaDurationConversion: Boolean = UseJavaDurationConversion.enabledByDefault, - private val newStrictNullChecks: Boolean = NewStrictNullChecks.enabledByDefault, + newStrictNullChecks: Boolean = NewStrictNullChecks.enabledByDefault, ) : SimpleModule(KotlinModule::class.java.name, PackageVersion.VERSION) { /* * Prior to 2.18, an older Enum called SingletonSupport was used to manage feature. @@ -67,8 +66,6 @@ class KotlinModule private constructor( ) val enabledSingletonSupport: Boolean get() = singletonSupport - private val oldStrictNullChecks: Boolean = strictNullChecks - // To reduce the amount of destructive changes, no properties will be added to the public. val strictNullChecks: Boolean = if (strictNullChecks) { if (newStrictNullChecks) { @@ -113,7 +110,7 @@ class KotlinModule private constructor( val cache = ReflectionCache(reflectionCacheSize) - context.addValueInstantiators(KotlinInstantiators(cache, nullToEmptyCollection, nullToEmptyMap, nullIsSameAsDefault, oldStrictNullChecks)) + context.addValueInstantiators(KotlinInstantiators(cache, nullToEmptyCollection, nullToEmptyMap, nullIsSameAsDefault)) if (singletonSupport) { context.addBeanDeserializerModifier(KotlinBeanDeserializerModifier) @@ -128,7 +125,7 @@ class KotlinModule private constructor( useJavaDurationConversion )) context.appendAnnotationIntrospector( - KotlinNamesAnnotationIntrospector(cache, newStrictNullChecks, kotlinPropertyNameAsImplicitName) + KotlinNamesAnnotationIntrospector(cache, strictNullChecks, kotlinPropertyNameAsImplicitName) ) context.addDeserializers(KotlinDeserializers(cache, useJavaDurationConversion)) diff --git a/src/main/kotlin/com/fasterxml/jackson/module/kotlin/KotlinValueInstantiator.kt b/src/main/kotlin/com/fasterxml/jackson/module/kotlin/KotlinValueInstantiator.kt index 99fbaab7..06f61113 100644 --- a/src/main/kotlin/com/fasterxml/jackson/module/kotlin/KotlinValueInstantiator.kt +++ b/src/main/kotlin/com/fasterxml/jackson/module/kotlin/KotlinValueInstantiator.kt @@ -22,16 +22,13 @@ internal class KotlinValueInstantiator( private val cache: ReflectionCache, private val nullToEmptyCollection: Boolean, private val nullToEmptyMap: Boolean, - private val nullIsSameAsDefault: Boolean, - private val strictNullChecks: Boolean + private val nullIsSameAsDefault: Boolean ) : StdValueInstantiator(src) { private fun JavaType.requireEmptyValue() = (nullToEmptyCollection && this.isCollectionLikeType) || (nullToEmptyMap && this.isMapLikeType) private fun KType.isGenericTypeVar() = javaType is TypeVariable<*> - private fun List.markedNonNullAt(index: Int) = getOrNull(index)?.type?.isMarkedNullable == false - // If the argument is a value class that wraps nullable and non-null, // and the input is explicit null, the value class is instantiated with null as input. private fun requireValueClassSpecialNullValue( @@ -104,35 +101,6 @@ internal class KotlinValueInstantiator( ).wrapWithPath(this.valueClass, pname) } } - } else if (strictNullChecks) { - val arguments = paramType.arguments - - // To make the behavior the same as deserialization of each element using NullsFailProvider, - // first wrapWithPath with paramVal and key. - val ex = when { - propType.isCollectionLikeType && arguments.markedNonNullAt(0) -> { - (paramVal as Collection<*>).indexOf(null).takeIf { it >= 0 }?.let { - InvalidNullException.from(ctxt, jsonProp.fullName, jsonProp.type) - .wrapWithPath(paramVal, it) - } - } - propType.isMapLikeType && arguments.markedNonNullAt(1) -> { - (paramVal as Map<*, *>).entries.find { (_, v) -> v == null }?.let { (k, _) -> - InvalidNullException.from(ctxt, jsonProp.fullName, jsonProp.type) - .wrapWithPath(paramVal, k.toString()) - } - } - propType.isArrayType && arguments.markedNonNullAt(0) -> { - (paramVal as Array<*>).indexOf(null).takeIf { it >= 0 }?.let { - InvalidNullException.from(ctxt, jsonProp.fullName, jsonProp.type) - .wrapWithPath(paramVal, it) - } - } - else -> null - } - - // Then, wrapWithPath with this property. - ex?.let { throw it.wrapWithPath(this.valueClass, jsonProp.name) } } bucket[paramDef] = paramVal @@ -151,7 +119,6 @@ internal class KotlinInstantiators( private val nullToEmptyCollection: Boolean, private val nullToEmptyMap: Boolean, private val nullIsSameAsDefault: Boolean, - private val strictNullChecks: Boolean ) : ValueInstantiators { override fun findValueInstantiator( deserConfig: DeserializationConfig, @@ -165,8 +132,7 @@ internal class KotlinInstantiators( cache, nullToEmptyCollection, nullToEmptyMap, - nullIsSameAsDefault, - strictNullChecks + nullIsSameAsDefault ) } else { // TODO: return defaultInstantiator and let default method parameters and nullability go unused? or die with exception: diff --git a/src/test/kotlin/com/fasterxml/jackson/module/kotlin/KotlinInstantiatorsTest.kt b/src/test/kotlin/com/fasterxml/jackson/module/kotlin/KotlinInstantiatorsTest.kt index 50286ba7..a223908e 100644 --- a/src/test/kotlin/com/fasterxml/jackson/module/kotlin/KotlinInstantiatorsTest.kt +++ b/src/test/kotlin/com/fasterxml/jackson/module/kotlin/KotlinInstantiatorsTest.kt @@ -11,8 +11,7 @@ class KotlinInstantiatorsTest { ReflectionCache(10), nullToEmptyCollection = false, nullToEmptyMap = false, - nullIsSameAsDefault = false, - strictNullChecks = false + nullIsSameAsDefault = false ) @Test diff --git a/src/test/kotlin/com/fasterxml/jackson/module/kotlin/kogeraIntegration/deser/StrictNullChecksTest.kt b/src/test/kotlin/com/fasterxml/jackson/module/kotlin/kogeraIntegration/deser/StrictNullChecksTest.kt index d0d74f09..3cad0edc 100644 --- a/src/test/kotlin/com/fasterxml/jackson/module/kotlin/kogeraIntegration/deser/StrictNullChecksTest.kt +++ b/src/test/kotlin/com/fasterxml/jackson/module/kotlin/kogeraIntegration/deser/StrictNullChecksTest.kt @@ -16,7 +16,7 @@ class StrictNullChecksTest { val mapper: ObjectMapper = ObjectMapper() .registerModule( KotlinModule.Builder() - .enable(KotlinFeature.NewStrictNullChecks) + .enable(KotlinFeature.StrictNullChecks) .build() ) diff --git a/src/test/kotlin/com/fasterxml/jackson/module/kotlin/test/StrictNullChecksTest.kt b/src/test/kotlin/com/fasterxml/jackson/module/kotlin/test/StrictNullChecksTest.kt index 2681e846..19178471 100644 --- a/src/test/kotlin/com/fasterxml/jackson/module/kotlin/test/StrictNullChecksTest.kt +++ b/src/test/kotlin/com/fasterxml/jackson/module/kotlin/test/StrictNullChecksTest.kt @@ -2,7 +2,7 @@ package com.fasterxml.jackson.module.kotlin.test import com.fasterxml.jackson.databind.ObjectMapper import com.fasterxml.jackson.databind.exc.InvalidNullException -import com.fasterxml.jackson.module.kotlin.KotlinFeature.NewStrictNullChecks +import com.fasterxml.jackson.module.kotlin.KotlinFeature import com.fasterxml.jackson.module.kotlin.kotlinModule import com.fasterxml.jackson.module.kotlin.readValue import org.junit.jupiter.api.Assertions.assertArrayEquals @@ -13,7 +13,7 @@ import org.junit.jupiter.api.assertThrows import kotlin.test.assertNull class StrictNullChecksTest { - private val mapper = ObjectMapper().registerModule(kotlinModule { enable(NewStrictNullChecks) }) + private val mapper = ObjectMapper().registerModule(kotlinModule { enable(KotlinFeature.StrictNullChecks) }) /** collection tests */ diff --git a/src/test/kotlin/com/fasterxml/jackson/module/kotlin/test/StrictNullChecksTestOld.kt b/src/test/kotlin/com/fasterxml/jackson/module/kotlin/test/StrictNullChecksTestOld.kt deleted file mode 100644 index d2301d27..00000000 --- a/src/test/kotlin/com/fasterxml/jackson/module/kotlin/test/StrictNullChecksTestOld.kt +++ /dev/null @@ -1,161 +0,0 @@ -package com.fasterxml.jackson.module.kotlin.test - -import com.fasterxml.jackson.databind.exc.InvalidNullException -import com.fasterxml.jackson.module.kotlin.KotlinFeature -import com.fasterxml.jackson.module.kotlin.jacksonObjectMapper -import com.fasterxml.jackson.module.kotlin.readValue -import org.junit.jupiter.api.Assertions.assertArrayEquals -import org.junit.jupiter.api.Assertions.assertEquals -import org.junit.jupiter.api.Disabled -import org.junit.jupiter.api.Test -import org.junit.jupiter.api.assertThrows -import kotlin.test.assertNull - -class StrictNullChecksTestOld { - private val mapper = jacksonObjectMapper { - disable(KotlinFeature.NewStrictNullChecks) - enable(KotlinFeature.StrictNullChecks) - } - - /** collection tests */ - - private data class ClassWithListOfNullableInt(val samples: List) - - @Test - fun testListOfNullableInt() { - val json = """{"samples":[1, null]}""" - val stateObj = mapper.readValue(json) - assertEquals(listOf(1, null), stateObj.samples) - } - - private data class ClassWithListOfInt(val samples: List) - - @Test - fun testListOfInt() { - assertThrows { - val json = """{"samples":[1, null]}""" - mapper.readValue(json) - } - } - - private data class ClassWithNullableListOfInt(val samples: List?) - - @Test - fun testNullableListOfInt() { - val json = """{"samples": null}""" - val stateObj = mapper.readValue(json) - assertNull(stateObj.samples) - } - - /** array tests */ - - private data class ClassWithArrayOfNullableInt(val samples: Array) - - @Test - fun testArrayOfNullableInt() { - val json = """{"samples":[1, null]}""" - val stateObj = mapper.readValue(json) - assertArrayEquals(arrayOf(1, null), stateObj.samples) - } - - private data class ClassWithArrayOfInt(val samples: Array) - - @Test - fun testArrayOfInt() { - assertThrows { - val json = """{"samples":[1, null]}""" - mapper.readValue(json) - } - } - - private data class ClassWithNullableArrayOfInt(val samples: Array?) - - @Test - fun testNullableArrayOfInt() { - val json = """{"samples": null}""" - val stateObj = mapper.readValue(json) - assertNull(stateObj.samples) - } - - /** map tests */ - - private data class ClassWithMapOfStringToNullableInt(val samples: Map) - - @Test - fun testMapOfStringToNullableInt() { - val json = """{ "samples": { "key": null } }""" - val stateObj = mapper.readValue(json) - assertEquals(mapOf("key" to null), stateObj.samples) - } - - private data class ClassWithMapOfStringToInt(val samples: Map) - - @Test - fun testMapOfStringToIntWithNullValue() { - assertThrows { - val json = """{ "samples": { "key": null } }""" - mapper.readValue(json) - } - } - - private data class ClassWithNullableMapOfStringToInt(val samples: Map?) - - @Test - fun testNullableMapOfStringToInt() { - val json = """{"samples": null}""" - val stateObj = mapper.readValue(json) - assertNull(stateObj.samples) - } - - /** generics test */ - - private data class TestClass(val samples: T) - - @Test - fun testListOfGeneric() { - val json = """{"samples":[1, 2]}""" - val stateObj = mapper.readValue>>(json) - assertEquals(listOf(1, 2), stateObj.samples) - } - - @Disabled // this is a hard problem to solve and is currently not addressed - @Test - fun testListOfGenericWithNullValue() { - assertThrows { - val json = """{"samples":[1, null]}""" - mapper.readValue>>(json) - } - } - - @Test - fun testMapOfGeneric() { - val json = """{ "samples": { "key": 1 } }""" - val stateObj = mapper.readValue>>(json) - assertEquals(mapOf("key" to 1), stateObj.samples) - } - - @Disabled // this is a hard problem to solve and is currently not addressed - @Test - fun testMapOfGenericWithNullValue() { - assertThrows { - val json = """{ "samples": { "key": null } }""" - mapper.readValue>>(json) - } - } - - @Test - fun testArrayOfGeneric() { - val json = """{"samples":[1, 2]}""" - val stateObj = mapper.readValue>>(json) - assertArrayEquals(arrayOf(1, 2), stateObj.samples) - } - - @Disabled // this is a hard problem to solve and is currently not addressed - @Test - fun testArrayOfGenericWithNullValue() { - assertThrows { - val json = """{"samples":[1, null]}""" - mapper.readValue>>(json) - } - } -} diff --git a/src/test/kotlin/com/fasterxml/jackson/module/kotlin/test/github/GitHub757.kt b/src/test/kotlin/com/fasterxml/jackson/module/kotlin/test/github/GitHub757.kt index 0245c9b8..f9c00f50 100644 --- a/src/test/kotlin/com/fasterxml/jackson/module/kotlin/test/github/GitHub757.kt +++ b/src/test/kotlin/com/fasterxml/jackson/module/kotlin/test/github/GitHub757.kt @@ -11,7 +11,7 @@ class GitHub757 { @Test fun test() { val kotlinModule = KotlinModule.Builder() - .enable(KotlinFeature.NewStrictNullChecks) + .enable(KotlinFeature.StrictNullChecks) .build() val mapper = JsonMapper.builder() .addModule(kotlinModule) diff --git a/src/test/kotlin/com/fasterxml/jackson/module/kotlin/test/github/GitHub976.kt b/src/test/kotlin/com/fasterxml/jackson/module/kotlin/test/github/GitHub976.kt index 27e2c643..dc448e14 100644 --- a/src/test/kotlin/com/fasterxml/jackson/module/kotlin/test/github/GitHub976.kt +++ b/src/test/kotlin/com/fasterxml/jackson/module/kotlin/test/github/GitHub976.kt @@ -18,14 +18,4 @@ class GitHub976 { om.readValue("""{"list": [""] }""".toByteArray(), PrimitiveList::class.java) } } - - @Test - fun newStrictNullChecksRegression() { - val om = jacksonObjectMapper { - enable(KotlinFeature.NewStrictNullChecks) - } - assertThrows { - om.readValue("""{"list": [""] }""".toByteArray(), PrimitiveList::class.java) - } - } }