diff --git a/release-notes/CREDITS-2.x b/release-notes/CREDITS-2.x index b6ef3c1a..e438bc6c 100644 --- a/release-notes/CREDITS-2.x +++ b/release-notes/CREDITS-2.x @@ -29,7 +29,6 @@ WrongWrong (@k163377) * #686: Add KotlinPropertyNameAsImplicitName option * #685: Streamline default value management for KotlinFeatures * #684: Update Kotlin Version to 1.6 -* #682: Remove MissingKotlinParameterException and replace with MismatchedInputException # 2.15.2 diff --git a/release-notes/VERSION-2.x b/release-notes/VERSION-2.x index 0b514256..3577629e 100644 --- a/release-notes/VERSION-2.x +++ b/release-notes/VERSION-2.x @@ -32,9 +32,6 @@ Co-maintainers: #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. -#682: Remove MissingKotlinParameterException and replace with MismatchedInputException - This change removes MissingKotlinParameterException and resolves #617. - This change is a prerequisite for future work to improve performance. 2.15.3 (12-Oct-2023) diff --git a/src/main/kotlin/tools/jackson/module/kotlin/Exceptions.kt b/src/main/kotlin/tools/jackson/module/kotlin/Exceptions.kt new file mode 100644 index 00000000..506bb328 --- /dev/null +++ b/src/main/kotlin/tools/jackson/module/kotlin/Exceptions.kt @@ -0,0 +1,40 @@ +package tools.jackson.module.kotlin + +import tools.jackson.core.JsonParser +import tools.jackson.databind.exc.MismatchedInputException +import java.io.Closeable +import kotlin.reflect.KParameter + +/** + * Specialized [JsonMappingException] sub-class used to indicate that a mandatory Kotlin constructor + * parameter was missing or null. + */ +@Deprecated( + "It is recommended that MismatchedInputException be referenced when possible," + + " as the change is discussed for 2.17 and later." + + " See #617 for details.", + ReplaceWith( + "MismatchedInputException", + "com.fasterxml.jackson.databind.exc.MismatchedInputException" + ), + DeprecationLevel.WARNING +) +// When deserialized by the JDK, the parameter property will be null, ignoring nullability. +// This is a temporary workaround for #572 and we will eventually remove this class. +class MissingKotlinParameterException( + @property:Deprecated( + "KParameter is not serializable and will be removed in 2.17 or later. See #572 for details.", + level = DeprecationLevel.WARNING + ) + @Transient + val parameter: KParameter, + processor: JsonParser? = null, + msg: String +) : MismatchedInputException(processor, msg) { + @Deprecated("Use main constructor", ReplaceWith("MissingKotlinParameterException(KParameter, JsonParser?, String)")) + constructor( + parameter: KParameter, + processor: Closeable? = null, + msg: String + ) : this(parameter, processor as JsonParser, msg) +} diff --git a/src/main/kotlin/tools/jackson/module/kotlin/KotlinValueInstantiator.kt b/src/main/kotlin/tools/jackson/module/kotlin/KotlinValueInstantiator.kt index 347d3147..bb9b36fc 100644 --- a/src/main/kotlin/tools/jackson/module/kotlin/KotlinValueInstantiator.kt +++ b/src/main/kotlin/tools/jackson/module/kotlin/KotlinValueInstantiator.kt @@ -98,12 +98,10 @@ internal class KotlinValueInstantiator( // 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" + throw MissingKotlinParameterException( + parameter = paramDef, + processor = ctxt.parser, + 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" ).wrapWithPath(this.valueClass, jsonProp.name) } } @@ -129,10 +127,10 @@ internal class KotlinValueInstantiator( } if (paramTypeStr != null && itemType != null) { - throw MismatchedInputException.from( - ctxt.parser, - propType, - "Instantiation of $itemType $paramTypeStr failed for JSON property ${jsonProp.name} due to null value in a $paramTypeStr that does not allow null values" + throw MissingKotlinParameterException( + parameter = paramDef, + processor = ctxt.parser, + msg = "Instantiation of $itemType $paramType failed for JSON property ${jsonProp.name} due to null value in a $paramType that does not allow null values" ).wrapWithPath(this.valueClass, jsonProp.name) } } diff --git a/src/test/kotlin/tools/jackson/module/kotlin/MissingKotlinParameterExceptionTest.kt b/src/test/kotlin/tools/jackson/module/kotlin/MissingKotlinParameterExceptionTest.kt new file mode 100644 index 00000000..fa7c388f --- /dev/null +++ b/src/test/kotlin/tools/jackson/module/kotlin/MissingKotlinParameterExceptionTest.kt @@ -0,0 +1,20 @@ +package tools.jackson.module.kotlin + +import org.junit.Test +import kotlin.test.assertNotNull +import kotlin.test.assertNull + +class MissingKotlinParameterExceptionTest { + @Test + fun jdkSerializabilityTest() { + val param = ::MissingKotlinParameterException.parameters.first() + val ex = MissingKotlinParameterException(param, null, "test") + + val serialized = jdkSerialize(ex) + val deserialized = jdkDeserialize(serialized) + + assertNotNull(deserialized) + // see comment at MissingKotlinParameterException + assertNull(deserialized.parameter) + } +} diff --git a/src/test/kotlin/tools/jackson/module/kotlin/test/NullToDefaultTests.kt b/src/test/kotlin/tools/jackson/module/kotlin/test/NullToDefaultTests.kt index 7d67e293..ee093bc4 100644 --- a/src/test/kotlin/tools/jackson/module/kotlin/test/NullToDefaultTests.kt +++ b/src/test/kotlin/tools/jackson/module/kotlin/test/NullToDefaultTests.kt @@ -1,8 +1,8 @@ package tools.jackson.module.kotlin.test import tools.jackson.databind.json.JsonMapper -import tools.jackson.databind.exc.MismatchedInputException import tools.jackson.module.kotlin.KotlinFeature.NullIsSameAsDefault +import tools.jackson.module.kotlin.MissingKotlinParameterException import tools.jackson.module.kotlin.kotlinModule import tools.jackson.module.kotlin.readValue import org.junit.Assert @@ -144,7 +144,7 @@ class TestNullToDefault { Assert.assertEquals(true, item.canBeProcessed) } - @Test(expected = MismatchedInputException::class) + @Test(expected = MissingKotlinParameterException::class) fun shouldThrowExceptionWhenProvidedNullForNotNullFieldWithoutDefault() { createMapper(true).readValue( """{ diff --git a/src/test/kotlin/tools/jackson/module/kotlin/test/StrictNullChecksTest.kt b/src/test/kotlin/tools/jackson/module/kotlin/test/StrictNullChecksTest.kt index 18d586a6..733a55e2 100644 --- a/src/test/kotlin/tools/jackson/module/kotlin/test/StrictNullChecksTest.kt +++ b/src/test/kotlin/tools/jackson/module/kotlin/test/StrictNullChecksTest.kt @@ -1,8 +1,8 @@ package tools.jackson.module.kotlin.test -import tools.jackson.databind.exc.MismatchedInputException import tools.jackson.databind.json.JsonMapper import tools.jackson.module.kotlin.KotlinFeature.StrictNullChecks +import tools.jackson.module.kotlin.MissingKotlinParameterException import tools.jackson.module.kotlin.kotlinModule import tools.jackson.module.kotlin.readValue import org.hamcrest.CoreMatchers.equalTo @@ -29,7 +29,7 @@ class StrictNullChecksTest { private data class ClassWithListOfInt(val samples: List) - @Test(expected = MismatchedInputException::class) + @Test(expected = MissingKotlinParameterException::class) fun testListOfInt() { val json = """{"samples":[1, null]}""" mapper.readValue(json) @@ -57,7 +57,7 @@ class StrictNullChecksTest { private data class ClassWithArrayOfInt(val samples: Array) - @Test(expected = MismatchedInputException::class) + @Test(expected = MissingKotlinParameterException::class) fun testArrayOfInt() { val json = """{"samples":[1, null]}""" mapper.readValue(json) @@ -85,7 +85,7 @@ class StrictNullChecksTest { private data class ClassWithMapOfStringToInt(val samples: Map) - @Test(expected = MismatchedInputException::class) + @Test(expected = MissingKotlinParameterException::class) fun testMapOfStringToIntWithNullValue() { val json = """{ "samples": { "key": null } }""" mapper.readValue(json) @@ -112,7 +112,7 @@ class StrictNullChecksTest { } @Ignore // this is a hard problem to solve and is currently not addressed - @Test(expected = MismatchedInputException::class) + @Test(expected = MissingKotlinParameterException::class) fun testListOfGenericWithNullValue() { val json = """{"samples":[1, null]}""" mapper.readValue>>(json) @@ -126,7 +126,7 @@ class StrictNullChecksTest { } @Ignore // this is a hard problem to solve and is currently not addressed - @Test(expected = MismatchedInputException::class) + @Test(expected = MissingKotlinParameterException::class) fun testMapOfGenericWithNullValue() { val json = """{ "samples": { "key": null } }""" mapper.readValue>>(json) @@ -140,7 +140,7 @@ class StrictNullChecksTest { } @Ignore // this is a hard problem to solve and is currently not addressed - @Test(expected = MismatchedInputException::class) + @Test(expected = MissingKotlinParameterException::class) fun testArrayOfGenericWithNullValue() { val json = """{"samples":[1, null]}""" mapper.readValue>>(json) diff --git a/src/test/kotlin/tools/jackson/module/kotlin/test/github/Github168.kt b/src/test/kotlin/tools/jackson/module/kotlin/test/github/Github168.kt index 0bc4b35d..57450899 100644 --- a/src/test/kotlin/tools/jackson/module/kotlin/test/github/Github168.kt +++ b/src/test/kotlin/tools/jackson/module/kotlin/test/github/Github168.kt @@ -2,7 +2,7 @@ package tools.jackson.module.kotlin.test.github import com.fasterxml.jackson.annotation.JsonProperty import tools.jackson.databind.ObjectMapper -import tools.jackson.databind.exc.MismatchedInputException +import tools.jackson.module.kotlin.MissingKotlinParameterException import tools.jackson.module.kotlin.jacksonObjectMapper import tools.jackson.module.kotlin.readValue import org.junit.Test @@ -21,7 +21,7 @@ class TestGithub168 { assertEquals("whatever", obj.baz) } - @Test(expected = MismatchedInputException::class) + @Test(expected = MissingKotlinParameterException::class) fun testIfRequiredIsReallyRequiredWhenAbsent() { val obj = jacksonObjectMapper().readValue("""{"baz":"whatever"}""") assertEquals("whatever", obj.baz) diff --git a/src/test/kotlin/tools/jackson/module/kotlin/test/github/Github32.kt b/src/test/kotlin/tools/jackson/module/kotlin/test/github/Github32.kt index 0602e993..11c1555d 100644 --- a/src/test/kotlin/tools/jackson/module/kotlin/test/github/Github32.kt +++ b/src/test/kotlin/tools/jackson/module/kotlin/test/github/Github32.kt @@ -1,16 +1,15 @@ package tools.jackson.module.kotlin.test.github -import tools.jackson.databind.DatabindException -import tools.jackson.databind.exc.MismatchedInputException +import tools.jackson.module.kotlin.MissingKotlinParameterException import tools.jackson.module.kotlin.jacksonObjectMapper import tools.jackson.module.kotlin.readValue import org.hamcrest.CustomTypeSafeMatcher import org.junit.Rule import org.junit.Test import org.junit.rules.ExpectedException +import tools.jackson.databind.DatabindException import kotlin.reflect.KParameter - class TestGithub32 { @Rule @@ -94,7 +93,6 @@ class TestGithub32 { } - private data class Person(val firstName: String, val lastName: String) private data class WrapperWithArgsContructor(val person: Person) @@ -106,20 +104,16 @@ private data class Crowd(val people: List) private fun missingFirstNameParameter() = missingConstructorParam(::Person.parameters[0]) -private fun missingConstructorParam( - param: KParameter -) = object : CustomTypeSafeMatcher( - "MissingKotlinParameterException with missing `${param.name}` parameter" -) { - override fun matchesSafely(e: MismatchedInputException): Boolean = param.name == e.path.last().propertyName +private fun missingConstructorParam(param: KParameter) = object : CustomTypeSafeMatcher("MissingKotlinParameterException with missing `${param.name}` parameter") { + override fun matchesSafely(e: MissingKotlinParameterException): Boolean = e.parameter.equals(param) } -private fun pathMatches(path: String) = object : CustomTypeSafeMatcher("MissingKotlinParameterException with path `$path`") { - override fun matchesSafely(e: MismatchedInputException): Boolean = e.getHumanReadablePath().equals(path) +private fun pathMatches(path: String) = object : CustomTypeSafeMatcher("MissingKotlinParameterException with path `$path`") { + override fun matchesSafely(e: tools.jackson.module.kotlin.MissingKotlinParameterException): Boolean = e.getHumanReadablePath().equals(path) } -private fun location(line: Int, column: Int) = object : CustomTypeSafeMatcher("MissingKotlinParameterException with location (line=$line, column=$column)") { - override fun matchesSafely(e: MismatchedInputException): Boolean { +private fun location(line: Int, column: Int) = object : CustomTypeSafeMatcher("MissingKotlinParameterException with location (line=$line, column=$column)") { + override fun matchesSafely(e: MissingKotlinParameterException): Boolean { return e.location != null && line.equals(e.location.lineNr) && column.equals(e.location.columnNr) } }