diff --git a/release-notes/CREDITS-2.x b/release-notes/CREDITS-2.x index 76d1c3d4..a1f588fc 100644 --- a/release-notes/CREDITS-2.x +++ b/release-notes/CREDITS-2.x @@ -18,6 +18,7 @@ Contributors: # 2.17.0 (not yet released) WrongWrong (@k163377) +* #743: Fix handling of vararg deserialization. * #742: Minor performance improvements to NullToEmptyCollection/Map. * #741: Changed to allow KotlinFeature to be set in the function that registers a KotlinModule. * #740: Reduce conversion cache from Executable to KFunction. diff --git a/release-notes/VERSION-2.x b/release-notes/VERSION-2.x index 0e6d0099..7930a0aa 100644 --- a/release-notes/VERSION-2.x +++ b/release-notes/VERSION-2.x @@ -18,6 +18,8 @@ Co-maintainers: 2.17.0 (not yet released) +* #743: The handling of deserialization using vararg arguments has been improved to allow deserialization even when the input to the vararg argument is undefined. + In addition, vararg arguments are now reported as non-required. #742: Minor performance improvements to NullToEmptyCollection/Map. #741: Changed to allow KotlinFeature to be set in the function that registers a KotlinModule. The `jacksonObjectMapper {}` and `registerKotlinModule {}` lambdas allow configuration for KotlinModule. 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 7af632b4..073ee2a9 100644 --- a/src/main/kotlin/com/fasterxml/jackson/module/kotlin/KotlinAnnotationIntrospector.kt +++ b/src/main/kotlin/com/fasterxml/jackson/module/kotlin/KotlinAnnotationIntrospector.kt @@ -207,13 +207,12 @@ internal class KotlinAnnotationIntrospector( private fun KFunction<*>.isParameterRequired(index: Int): Boolean { val param = parameters[index] val paramType = param.type - val javaType = paramType.javaType - val isPrimitive = when (javaType) { + val isPrimitive = when (val javaType = paramType.javaType) { is Class<*> -> javaType.isPrimitive else -> false } - return !paramType.isMarkedNullable && !param.isOptional && + return !paramType.isMarkedNullable && !param.isOptional && !param.isVararg && !(isPrimitive && !context.isEnabled(DeserializationFeature.FAIL_ON_NULL_FOR_PRIMITIVES)) } 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 06086531..a2384138 100644 --- a/src/main/kotlin/com/fasterxml/jackson/module/kotlin/KotlinValueInstantiator.kt +++ b/src/main/kotlin/com/fasterxml/jackson/module/kotlin/KotlinValueInstantiator.kt @@ -74,7 +74,7 @@ internal class KotlinValueInstantiator( tempParamVal } else { when { - paramDef.isOptional -> return@forEachIndexed + paramDef.isOptional || paramDef.isVararg -> return@forEachIndexed // do not try to create any object if it is nullable and the value is missing paramType.isMarkedNullable -> null // Primitive types always try to get from a buffer, considering several settings diff --git a/src/test/kotlin/com/fasterxml/jackson/module/kotlin/test/PropertyRequirednessTests.kt b/src/test/kotlin/com/fasterxml/jackson/module/kotlin/test/PropertyRequirednessTests.kt index a3fe2e89..492609f0 100644 --- a/src/test/kotlin/com/fasterxml/jackson/module/kotlin/test/PropertyRequirednessTests.kt +++ b/src/test/kotlin/com/fasterxml/jackson/module/kotlin/test/PropertyRequirednessTests.kt @@ -76,7 +76,7 @@ class TestPropertyRequiredness { // --- - private data class TestDataClass( + private class TestDataClass( val a: Int, val b: Int?, val c: Int = 5, @@ -85,6 +85,7 @@ class TestPropertyRequiredness { val f: TestParamClass?, val g: TestParamClass = TestParamClass(), val h: TestParamClass? = TestParamClass(), + vararg val i: Int, @JsonProperty("x", required = true) val x: Int?, // TODO: either error in test case with this not being on the property getter, or error in introspection not seeing this on the constructor parameter @get:JsonProperty("z", required = true) val z: Int ) @@ -117,6 +118,9 @@ class TestPropertyRequiredness { "h".isOptionalForSerializationOf(testClass, mapper) "h".isOptionalForDeserializationOf(testClass, mapper) + "i".isRequiredForSerializationOf(testClass, mapper) + "i".isOptionalForDeserializationOf(testClass, mapper) + "x".isRequiredForDeserializationOf(testClass, mapper) "x".isOptionalForSerializationOf(testClass, mapper) diff --git a/src/test/kotlin/com/fasterxml/jackson/module/kotlin/test/VarargDeserTest.kt b/src/test/kotlin/com/fasterxml/jackson/module/kotlin/test/VarargDeserTest.kt new file mode 100644 index 00000000..0e9c067b --- /dev/null +++ b/src/test/kotlin/com/fasterxml/jackson/module/kotlin/test/VarargDeserTest.kt @@ -0,0 +1,117 @@ +package com.fasterxml.jackson.module.kotlin.test + +import com.fasterxml.jackson.module.kotlin.jacksonObjectMapper +import com.fasterxml.jackson.module.kotlin.readValue +import junit.framework.TestCase.assertEquals +import junit.framework.TestCase.assertTrue +import org.junit.Ignore +import org.junit.experimental.runners.Enclosed +import org.junit.runner.RunWith +import kotlin.test.Test + +// from https://github.com/ProjectMapK/jackson-module-kogera/blob/0631cd3b07c7fb6971a00ac1f6811b4367a1720e/src/test/kotlin/io/github/projectmapk/jackson/module/kogera/zIntegration/deser/VarargTest.kt#L1 +@RunWith(Enclosed::class) +class VarargDeserTest { + @Ignore + companion object { + val mapper = jacksonObjectMapper() + } + + @Ignore + class OnlyVararg(vararg val v: Int) + + class OnlyVarargTest { + @Test + fun hasArgs() { + val r = mapper.readValue("""{"v":[1,2,3]}""") + assertEquals(listOf(1, 2, 3), r.v.asList()) + } + + @Test + fun empty() { + val r = mapper.readValue("""{"v":[]}""") + assertTrue(r.v.isEmpty()) + } + + @Test + fun undefined() { + val r = mapper.readValue("""{}""") + assertTrue(r.v.isEmpty()) + } + } + + @Ignore + class HeadVararg(vararg val v: Int?, val i: Int) + + class HeadVarargTest { + @Test + fun hasArgs() { + val r = mapper.readValue("""{"i":0,"v":[1,2,null]}""") + assertEquals(listOf(1, 2, null), r.v.asList()) + assertEquals(0, r.i) + } + + @Test + fun empty() { + val r = mapper.readValue("""{"i":0,"v":[]}""") + assertTrue(r.v.isEmpty()) + assertEquals(0, r.i) + } + + @Test + fun undefined() { + val r = mapper.readValue("""{"i":0}""") + assertTrue(r.v.isEmpty()) + assertEquals(0, r.i) + } + } + + @Ignore + class TailVararg(val i: Int, vararg val v: String) + + class TailVarargTest { + @Test + fun hasArgs() { + val r = mapper.readValue("""{"i":0,"v":["foo","bar","baz"]}""") + assertEquals(listOf("foo", "bar", "baz"), r.v.asList()) + assertEquals(0, r.i) + } + + @Test + fun empty() { + val r = mapper.readValue("""{"i":0,"v":[]}""") + assertTrue(r.v.isEmpty()) + assertEquals(0, r.i) + } + + @Test + fun undefined() { + val r = mapper.readValue("""{"i":0}""") + assertTrue(r.v.isEmpty()) + assertEquals(0, r.i) + } + } + + @Ignore + class HasDefaultVararg(vararg val v: String? = arrayOf("foo", "bar")) + + class HasDefaultVarargTest { + @Test + fun hasArgs() { + val r = mapper.readValue("""{"v":["foo","bar",null]}""") + assertEquals(listOf("foo", "bar", null), r.v.asList()) + } + + @Test + fun empty() { + val r = mapper.readValue("""{"v":[]}""") + assertTrue(r.v.isEmpty()) + } + + @Test + fun undefined() { + val r = mapper.readValue("""{}""") + assertEquals(listOf("foo", "bar"), r.v.asList()) + } + } +}