From 45676cee263403ff0497d27ec1df94d783393778 Mon Sep 17 00:00:00 2001 From: apatrida <39ee8d8de9f9607bb1e84ae6d27cde373ff22ab3> Date: Sat, 26 Oct 2019 11:29:23 -0600 Subject: [PATCH] Add module configuration flag to treat null values as defaults instead of doing this without warning. --- .../kotlin/KotlinAnnotationIntrospector.kt | 9 +++++- .../jackson/module/kotlin/KotlinModule.kt | 6 ++-- .../module/kotlin/KotlinValueInstantiator.kt | 10 +++--- .../module/kotlin/test/NullToDefault.kt | 31 ++++++++++++++++--- 4 files changed, 44 insertions(+), 12 deletions(-) diff --git a/src/main/kotlin/com/fasterxml/jackson/module/kotlin/KotlinAnnotationIntrospector.kt b/src/main/kotlin/com/fasterxml/jackson/module/kotlin/KotlinAnnotationIntrospector.kt index e07ea45d..5e979417 100644 --- a/src/main/kotlin/com/fasterxml/jackson/module/kotlin/KotlinAnnotationIntrospector.kt +++ b/src/main/kotlin/com/fasterxml/jackson/module/kotlin/KotlinAnnotationIntrospector.kt @@ -18,7 +18,14 @@ import kotlin.reflect.full.declaredMemberProperties import kotlin.reflect.jvm.* -internal class KotlinAnnotationIntrospector(private val context: Module.SetupContext, private val cache: ReflectionCache, private val nullToEmptyCollection: Boolean, private val nullToEmptyMap: Boolean) : NopAnnotationIntrospector() { +internal class KotlinAnnotationIntrospector(private val context: Module.SetupContext, + private val cache: ReflectionCache, + private val nullToEmptyCollection: Boolean, + private val nullToEmptyMap: Boolean, + private val nullIsSameAsDefault: Boolean) : NopAnnotationIntrospector() { + + // TODO: implement nullIsSameAsDefault flag, which represents when TRUE that if something has a default value, it can be passed a null to default it + // this likely impacts this class to be accurate about what COULD be considered required override fun hasRequiredMarker(m: AnnotatedMember): Boolean? = cache.javaMemberIsRequired(m) { 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 257bc08a..1ec3951a 100644 --- a/src/main/kotlin/com/fasterxml/jackson/module/kotlin/KotlinModule.kt +++ b/src/main/kotlin/com/fasterxml/jackson/module/kotlin/KotlinModule.kt @@ -27,7 +27,7 @@ fun Class<*>.isKotlinClass(): Boolean { return declaredAnnotations.any { it.annotationClass.java.name == metadataFqName } } -class KotlinModule(val reflectionCacheSize: Int = 512, val nullToEmptyCollection: Boolean = false, val nullToEmptyMap: Boolean = false) : SimpleModule(PackageVersion.VERSION) { +class KotlinModule(val reflectionCacheSize: Int = 512, val nullToEmptyCollection: Boolean = false, val nullToEmptyMap: Boolean = false, val nullisSameAsDefault: Boolean = false) : SimpleModule(PackageVersion.VERSION) { companion object { const val serialVersionUID = 1L } @@ -41,12 +41,12 @@ class KotlinModule(val reflectionCacheSize: Int = 512, val nullToEmptyCollection val cache = ReflectionCache(reflectionCacheSize) - context.addValueInstantiators(KotlinInstantiators(cache, nullToEmptyCollection, nullToEmptyMap)) + context.addValueInstantiators(KotlinInstantiators(cache, nullToEmptyCollection, nullToEmptyMap, nullisSameAsDefault)) // [module-kotlin#225]: keep Kotlin singletons as singletons context.addBeanDeserializerModifier(KotlinBeanDeserializerModifier) - context.insertAnnotationIntrospector(KotlinAnnotationIntrospector(context, cache, nullToEmptyCollection, nullToEmptyMap)) + context.insertAnnotationIntrospector(KotlinAnnotationIntrospector(context, cache, nullToEmptyCollection, nullToEmptyMap, nullisSameAsDefault)) context.appendAnnotationIntrospector(KotlinNamesAnnotationIntrospector(this, cache)) context.addDeserializers(KotlinDeserializers()) 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 7b530cd7..0b0c341d 100644 --- a/src/main/kotlin/com/fasterxml/jackson/module/kotlin/KotlinValueInstantiator.kt +++ b/src/main/kotlin/com/fasterxml/jackson/module/kotlin/KotlinValueInstantiator.kt @@ -24,7 +24,8 @@ internal class KotlinValueInstantiator( src: StdValueInstantiator, private val cache: ReflectionCache, private val nullToEmptyCollection: Boolean, - private val nullToEmptyMap: Boolean + private val nullToEmptyMap: Boolean, + private val nullIsSameAsDefault: Boolean ) : StdValueInstantiator(src) { @Suppress("UNCHECKED_CAST") override fun createFromObjectWith( @@ -91,7 +92,7 @@ internal class KotlinValueInstantiator( var paramVal = if (!isMissing || paramDef.isPrimitive() || jsonProp.hasInjectableValueId()) { val tempParamVal = buffer.getParameter(jsonProp) - if (tempParamVal == null && paramDef.isOptional) { + if (nullIsSameAsDefault && tempParamVal == null && paramDef.isOptional) { return@forEachIndexed } tempParamVal @@ -159,7 +160,8 @@ internal class KotlinValueInstantiator( internal class KotlinInstantiators( private val cache: ReflectionCache, private val nullToEmptyCollection: Boolean, - private val nullToEmptyMap: Boolean + private val nullToEmptyMap: Boolean, + private val nullIsSameAsDefault: Boolean ) : ValueInstantiators { override fun findValueInstantiator( deserConfig: DeserializationConfig, @@ -168,7 +170,7 @@ internal class KotlinInstantiators( ): ValueInstantiator { return if (beanDescriptor.beanClass.isKotlinClass()) { if (defaultInstantiator is StdValueInstantiator) { - KotlinValueInstantiator(defaultInstantiator, cache, nullToEmptyCollection, nullToEmptyMap) + KotlinValueInstantiator(defaultInstantiator, cache, nullToEmptyCollection, nullToEmptyMap, nullIsSameAsDefault) } else { // TODO: return defaultInstantiator and let default method parameters and nullability go unused? or die with exception: throw IllegalStateException("KotlinValueInstantiator requires that the default ValueInstantiator is StdValueInstantiator") diff --git a/src/test/kotlin/com/fasterxml/jackson/module/kotlin/test/NullToDefault.kt b/src/test/kotlin/com/fasterxml/jackson/module/kotlin/test/NullToDefault.kt index 0e69b92f..6c4ccf97 100644 --- a/src/test/kotlin/com/fasterxml/jackson/module/kotlin/test/NullToDefault.kt +++ b/src/test/kotlin/com/fasterxml/jackson/module/kotlin/test/NullToDefault.kt @@ -1,5 +1,7 @@ package com.fasterxml.jackson.module.kotlin.test +import com.fasterxml.jackson.databind.ObjectMapper +import com.fasterxml.jackson.module.kotlin.KotlinModule import com.fasterxml.jackson.module.kotlin.MissingKotlinParameterException import com.fasterxml.jackson.module.kotlin.jacksonObjectMapper import com.fasterxml.jackson.module.kotlin.readValue @@ -11,7 +13,7 @@ import org.junit.internal.runners.statements.ExpectException // @Ignore("not yet implemented, under discussion") class NullToDefault { - private fun createMapper() = jacksonObjectMapper() + private fun createMapper(allowDefaultingByNull: Boolean) = ObjectMapper().registerModule(KotlinModule(nullisSameAsDefault = allowDefaultingByNull)) private data class TestClass(val sku: Int = -1, val text: String, @@ -22,8 +24,29 @@ class NullToDefault { val order: Int = -1) @Test - fun shouldUseDefault() { - val item = createMapper().readValue( + fun shouldUseNullAsDefault() { + val item = createMapper(true).readValue( + """{ + "sku": "974", + "text": "plain", + "name": null, + "images": null, + "attribute": "19" + }""") + + Assert.assertTrue(item.sku == 974) + Assert.assertTrue(item.text == "plain") + @Suppress("SENSELESS_COMPARISON") + Assert.assertTrue(item.name != null) + Assert.assertTrue(item.images == null) + Assert.assertTrue(item.language == "uk") + Assert.assertTrue(item.attribute == 19) + Assert.assertTrue(item.order == -1) + } + + @Test(expected = MissingKotlinParameterException::class) + fun shouldNotUseNullAsDefault() { + val item = createMapper(false).readValue( """{ "sku": "974", "text": "plain", @@ -44,7 +67,7 @@ class NullToDefault { @Test(expected = MissingKotlinParameterException::class) fun errorIfNotDefault() { - val item = createMapper().readValue( + val item = createMapper(true).readValue( """{ "sku": "974", "text": null,