From 5dc7e21d42007854f7fdbbfa18066d3203e4721d Mon Sep 17 00:00:00 2001 From: wrongwrong Date: Sun, 6 Apr 2025 01:45:22 +0900 Subject: [PATCH 1/9] Added mismatch check for deserialization result --- .../jackson/module/kotlin/Extensions.kt | 31 +++++++++++++++++-- 1 file changed, 28 insertions(+), 3 deletions(-) diff --git a/src/main/kotlin/com/fasterxml/jackson/module/kotlin/Extensions.kt b/src/main/kotlin/com/fasterxml/jackson/module/kotlin/Extensions.kt index 2a8c3b1a2..edebdaffa 100644 --- a/src/main/kotlin/com/fasterxml/jackson/module/kotlin/Extensions.kt +++ b/src/main/kotlin/com/fasterxml/jackson/module/kotlin/Extensions.kt @@ -4,6 +4,7 @@ import com.fasterxml.jackson.core.JsonParser import com.fasterxml.jackson.core.TreeNode import com.fasterxml.jackson.core.type.TypeReference import com.fasterxml.jackson.databind.JsonDeserializer +import com.fasterxml.jackson.databind.JsonMappingException import com.fasterxml.jackson.databind.JsonNode import com.fasterxml.jackson.databind.JsonSerializer import com.fasterxml.jackson.databind.MappingIterator @@ -50,20 +51,44 @@ fun ObjectMapper.registerKotlinModule(initializer: KotlinModule.Builder.() -> Un inline fun jacksonTypeRef(): TypeReference = object: TypeReference() {} +/** + * It is public due to Kotlin restrictions, but should not be used externally. + */ +inline fun Any?.checkTypeMismatch(): T { + // Basically, this check assumes that T is non-null and the value is null. + // Since this can be caused by both input or ObjectMapper implementation errors, + // a more abstract JsonMappingException is thrown. + if (this !is T) { + throw JsonMappingException( + null, + "Deserialized value did not match the specified type; " + + "specified ${T::class.qualifiedName} but was ${this?.let { it::class.qualifiedName }}" + ) + } + return this +} + inline fun ObjectMapper.readValue(jp: JsonParser): T = readValue(jp, jacksonTypeRef()) + .checkTypeMismatch() inline fun ObjectMapper.readValues(jp: JsonParser): MappingIterator = readValues(jp, jacksonTypeRef()) -inline fun ObjectMapper.readValue(src: File): T = readValue(src, jacksonTypeRef()) -inline fun ObjectMapper.readValue(src: URL): T = readValue(src, jacksonTypeRef()) +inline fun ObjectMapper.readValue(src: File): T = readValue(src, jacksonTypeRef()).checkTypeMismatch() +inline fun ObjectMapper.readValue(src: URL): T = readValue(src, jacksonTypeRef()).checkTypeMismatch() inline fun ObjectMapper.readValue(content: String): T = readValue(content, jacksonTypeRef()) -inline fun ObjectMapper.readValue(src: Reader): T = readValue(src, jacksonTypeRef()) + .checkTypeMismatch() +inline fun ObjectMapper.readValue(src: Reader): T = readValue(src, jacksonTypeRef()).checkTypeMismatch() inline fun ObjectMapper.readValue(src: InputStream): T = readValue(src, jacksonTypeRef()) + .checkTypeMismatch() inline fun ObjectMapper.readValue(src: ByteArray): T = readValue(src, jacksonTypeRef()) + .checkTypeMismatch() inline fun ObjectMapper.treeToValue(n: TreeNode): T = readValue(this.treeAsTokens(n), jacksonTypeRef()) + .checkTypeMismatch() inline fun ObjectMapper.convertValue(from: Any?): T = convertValue(from, jacksonTypeRef()) + .checkTypeMismatch() inline fun ObjectReader.readValueTyped(jp: JsonParser): T = readValue(jp, jacksonTypeRef()) + .checkTypeMismatch() inline fun ObjectReader.readValuesTyped(jp: JsonParser): Iterator = readValues(jp, jacksonTypeRef()) inline fun ObjectReader.treeToValue(n: TreeNode): T? = readValue(this.treeAsTokens(n), jacksonTypeRef()) From d5008b22c4158de8f6fb02b4d68fe51a22718365 Mon Sep 17 00:00:00 2001 From: wrongwrong Date: Sun, 6 Apr 2025 01:53:25 +0900 Subject: [PATCH 2/9] Fix tests --- .../deser/valueClass/WithoutCustomDeserializeMethodTest.kt | 7 +++---- .../deserializer/SpecifiedForObjectMapperTest.kt | 7 +++---- 2 files changed, 6 insertions(+), 8 deletions(-) diff --git a/src/test/kotlin/com/fasterxml/jackson/module/kotlin/kogeraIntegration/deser/valueClass/WithoutCustomDeserializeMethodTest.kt b/src/test/kotlin/com/fasterxml/jackson/module/kotlin/kogeraIntegration/deser/valueClass/WithoutCustomDeserializeMethodTest.kt index 0423d2125..ab5709599 100644 --- a/src/test/kotlin/com/fasterxml/jackson/module/kotlin/kogeraIntegration/deser/valueClass/WithoutCustomDeserializeMethodTest.kt +++ b/src/test/kotlin/com/fasterxml/jackson/module/kotlin/kogeraIntegration/deser/valueClass/WithoutCustomDeserializeMethodTest.kt @@ -9,6 +9,7 @@ import org.junit.jupiter.api.Nested import org.junit.jupiter.api.assertThrows import org.junit.jupiter.api.Test import java.lang.reflect.InvocationTargetException +import kotlin.test.assertNotEquals class WithoutCustomDeserializeMethodTest { companion object { @@ -42,10 +43,8 @@ class WithoutCustomDeserializeMethodTest { // failing @Test fun nullString() { - org.junit.jupiter.api.assertThrows("#209 has been fixed.") { - val result = defaultMapper.readValue("null") - assertEquals(NullableObject(null), result) - } + val result = defaultMapper.readValue("null") + assertNotEquals(NullableObject(null), result, "kogera #209 has been fixed.") } } } diff --git a/src/test/kotlin/com/fasterxml/jackson/module/kotlin/kogeraIntegration/deser/valueClass/deserializer/SpecifiedForObjectMapperTest.kt b/src/test/kotlin/com/fasterxml/jackson/module/kotlin/kogeraIntegration/deser/valueClass/deserializer/SpecifiedForObjectMapperTest.kt index c57f41596..643883a74 100644 --- a/src/test/kotlin/com/fasterxml/jackson/module/kotlin/kogeraIntegration/deser/valueClass/deserializer/SpecifiedForObjectMapperTest.kt +++ b/src/test/kotlin/com/fasterxml/jackson/module/kotlin/kogeraIntegration/deser/valueClass/deserializer/SpecifiedForObjectMapperTest.kt @@ -9,6 +9,7 @@ import com.fasterxml.jackson.module.kotlin.kogeraIntegration.deser.valueClass.Pr import org.junit.jupiter.api.Assertions.assertEquals import org.junit.jupiter.api.Nested import org.junit.jupiter.api.Test +import kotlin.test.assertNotEquals class SpecifiedForObjectMapperTest { companion object { @@ -48,10 +49,8 @@ class SpecifiedForObjectMapperTest { // failing @Test fun nullString() { - org.junit.jupiter.api.assertThrows("#209 has been fixed.") { - val result = mapper.readValue("null") - assertEquals(NullableObject("null-value-deser"), result) - } + val result = mapper.readValue("null") + assertNotEquals(NullableObject("null-value-deser"), result, "kogera #209 has been fixed.") } } } From c89e2383a65593414026774452e5d6e8dce76bbe Mon Sep 17 00:00:00 2001 From: wrongwrong Date: Sun, 6 Apr 2025 02:33:52 +0900 Subject: [PATCH 3/9] Add tests --- .../jackson/module/kotlin/ReadValueTest.kt | 89 +++++++++++++++++++ .../jackson/module/kotlin/TestCommons.kt | 17 ++++ 2 files changed, 106 insertions(+) create mode 100644 src/test/kotlin/com/fasterxml/jackson/module/kotlin/ReadValueTest.kt diff --git a/src/test/kotlin/com/fasterxml/jackson/module/kotlin/ReadValueTest.kt b/src/test/kotlin/com/fasterxml/jackson/module/kotlin/ReadValueTest.kt new file mode 100644 index 000000000..10424d5ee --- /dev/null +++ b/src/test/kotlin/com/fasterxml/jackson/module/kotlin/ReadValueTest.kt @@ -0,0 +1,89 @@ +package com.fasterxml.jackson.module.kotlin + +import com.fasterxml.jackson.databind.JsonMappingException +import com.fasterxml.jackson.databind.node.NullNode +import org.junit.jupiter.api.Nested +import org.junit.jupiter.api.Test +import org.junit.jupiter.api.assertThrows +import java.io.StringReader + +class ReadValueTest { + @Nested + inner class CheckTypeMismatchTest { + @Test + fun jsonParser() { + val src = defaultMapper.createParser("null") + assertThrows { + defaultMapper.readValue(src) + } + } + + @Test + fun file() { + val src = createTempJson("null") + assertThrows { + defaultMapper.readValue(src) + } + } + + // Not implemented because a way to test without mocks was not found + // @Test + // fun url() { + // } + + @Test + fun string() { + val src = "null" + assertThrows { + defaultMapper.readValue(src) + } + } + + @Test + fun reader() { + val src = StringReader("null") + assertThrows { + defaultMapper.readValue(src) + } + } + + @Test + fun inputStream() { + val src = "null".byteInputStream() + assertThrows { + defaultMapper.readValue(src) + } + } + + @Test + fun byteArray() { + val src = "null".toByteArray() + assertThrows { + defaultMapper.readValue(src) + } + } + + @Test + fun treeToValueTreeNode() { + assertThrows { + defaultMapper.treeToValue(NullNode.instance) + } + } + + @Test + fun convertValueAny() { + assertThrows { + defaultMapper.convertValue(null) + } + } + + @Test + fun readValueTypedJsonParser() { + val reader = defaultMapper.reader() + val src = reader.createParser("null") + assertThrows { + reader.readValueTyped(src) + } + } + } +} diff --git a/src/test/kotlin/com/fasterxml/jackson/module/kotlin/TestCommons.kt b/src/test/kotlin/com/fasterxml/jackson/module/kotlin/TestCommons.kt index 3bd3e5bd8..327fc3a55 100644 --- a/src/test/kotlin/com/fasterxml/jackson/module/kotlin/TestCommons.kt +++ b/src/test/kotlin/com/fasterxml/jackson/module/kotlin/TestCommons.kt @@ -5,6 +5,10 @@ import com.fasterxml.jackson.core.util.DefaultIndenter import com.fasterxml.jackson.core.util.DefaultPrettyPrinter import com.fasterxml.jackson.databind.ObjectMapper import com.fasterxml.jackson.databind.ObjectWriter +import java.io.File +import java.io.FileOutputStream +import java.io.OutputStreamWriter +import java.nio.charset.StandardCharsets import kotlin.reflect.KParameter import kotlin.reflect.full.memberProperties import kotlin.reflect.full.primaryConstructor @@ -30,3 +34,16 @@ internal inline fun assertReflectEquals(expected: T, actual: T assertEquals(it.get(expected), it.get(actual)) } } + +internal fun createTempJson(json: String): File { + val file = File.createTempFile("temp", ".json") + file.deleteOnExit() + OutputStreamWriter( + FileOutputStream(file), + StandardCharsets.UTF_8 + ).use { writer -> + writer.write(json) + writer.flush() + } + return file +} From c9038f73d8c7621ccb4c3922a29ff6c42f34e5b6 Mon Sep 17 00:00:00 2001 From: wrongwrong Date: Sun, 6 Apr 2025 03:18:26 +0900 Subject: [PATCH 4/9] Added mismatch check for deserialization result --- .../jackson/module/kotlin/Extensions.kt | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/src/main/kotlin/com/fasterxml/jackson/module/kotlin/Extensions.kt b/src/main/kotlin/com/fasterxml/jackson/module/kotlin/Extensions.kt index edebdaffa..9d715df02 100644 --- a/src/main/kotlin/com/fasterxml/jackson/module/kotlin/Extensions.kt +++ b/src/main/kotlin/com/fasterxml/jackson/module/kotlin/Extensions.kt @@ -70,7 +70,13 @@ inline fun Any?.checkTypeMismatch(): T { inline fun ObjectMapper.readValue(jp: JsonParser): T = readValue(jp, jacksonTypeRef()) .checkTypeMismatch() -inline fun ObjectMapper.readValues(jp: JsonParser): MappingIterator = readValues(jp, jacksonTypeRef()) +inline fun ObjectMapper.readValues(jp: JsonParser): MappingIterator { + val values = readValues(jp, jacksonTypeRef()) + + return object : MappingIterator(values) { + override fun nextValue(): T = super.nextValue().checkTypeMismatch() + } +} inline fun ObjectMapper.readValue(src: File): T = readValue(src, jacksonTypeRef()).checkTypeMismatch() inline fun ObjectMapper.readValue(src: URL): T = readValue(src, jacksonTypeRef()).checkTypeMismatch() @@ -89,7 +95,13 @@ inline fun ObjectMapper.convertValue(from: Any?): T = convertValue(f inline fun ObjectReader.readValueTyped(jp: JsonParser): T = readValue(jp, jacksonTypeRef()) .checkTypeMismatch() -inline fun ObjectReader.readValuesTyped(jp: JsonParser): Iterator = readValues(jp, jacksonTypeRef()) +inline fun ObjectReader.readValuesTyped(jp: JsonParser): Iterator { + val values = readValues(jp, jacksonTypeRef()) + + return object : Iterator by values { + override fun next(): T = values.next().checkTypeMismatch() + } +} inline fun ObjectReader.treeToValue(n: TreeNode): T? = readValue(this.treeAsTokens(n), jacksonTypeRef()) inline fun ObjectMapper.addMixIn(): ObjectMapper = this.addMixIn(T::class.java, U::class.java) From 5d404ea5d4fc77a22eaffe9fcc35d75c3a1a4d73 Mon Sep 17 00:00:00 2001 From: wrongwrong Date: Sun, 6 Apr 2025 12:24:47 +0900 Subject: [PATCH 5/9] Add tests --- .../jackson/module/kotlin/ReadValuesTest.kt | 66 +++++++++++++++++++ 1 file changed, 66 insertions(+) create mode 100644 src/test/kotlin/com/fasterxml/jackson/module/kotlin/ReadValuesTest.kt diff --git a/src/test/kotlin/com/fasterxml/jackson/module/kotlin/ReadValuesTest.kt b/src/test/kotlin/com/fasterxml/jackson/module/kotlin/ReadValuesTest.kt new file mode 100644 index 000000000..4e55cab39 --- /dev/null +++ b/src/test/kotlin/com/fasterxml/jackson/module/kotlin/ReadValuesTest.kt @@ -0,0 +1,66 @@ +package com.fasterxml.jackson.module.kotlin + +import com.fasterxml.jackson.core.JsonParser +import com.fasterxml.jackson.databind.DeserializationContext +import com.fasterxml.jackson.databind.JsonMappingException +import com.fasterxml.jackson.databind.RuntimeJsonMappingException +import com.fasterxml.jackson.databind.deser.std.StdDeserializer +import com.fasterxml.jackson.databind.module.SimpleModule +import org.junit.jupiter.api.Nested +import org.junit.jupiter.api.Test +import org.junit.jupiter.api.assertThrows +import kotlin.test.assertEquals + +class ReadValuesTest { + class MyStrDeser : StdDeserializer(String::class.java) { + override fun deserialize( + p: JsonParser, + ctxt: DeserializationContext + ): String? = p.valueAsString.takeIf { it != "bar" } + } + + @Nested + inner class CheckTypeMismatchTest { + val mapper = jacksonObjectMapper().registerModule( + object : SimpleModule() { + init { + addDeserializer(String::class.java, MyStrDeser()) + } + } + )!! + + @Test + fun readValuesJsonParserNext() { + val src = mapper.createParser(""""foo"${"\n"}"bar"""") + val itr = mapper.readValues(src) + + assertEquals("foo", itr.next()) + assertThrows { + itr.next() + } + } + + @Test + fun readValuesJsonParserNextValue() { + val src = mapper.createParser(""""foo"${"\n"}"bar"""") + val itr = mapper.readValues(src) + + assertEquals("foo", itr.nextValue()) + assertThrows { + itr.nextValue() + } + } + + @Test + fun readValuesTypedJsonParser() { + val reader = mapper.reader() + val src = reader.createParser(""""foo"${"\n"}"bar"""") + val itr = reader.readValuesTyped(src) + + assertEquals("foo", itr.next()) + assertThrows { + itr.next() + } + } + } +} From ec2f84e5bf669878d1af521036e63ce2185fd543 Mon Sep 17 00:00:00 2001 From: wrongwrong Date: Sun, 6 Apr 2025 12:59:48 +0900 Subject: [PATCH 6/9] Fixed thrown exception to RuntimeJsonMappingException --- .../jackson/module/kotlin/Extensions.kt | 9 ++++---- .../jackson/module/kotlin/ReadValueTest.kt | 22 +++++++++---------- .../jackson/module/kotlin/ReadValuesTest.kt | 5 ++--- 3 files changed, 18 insertions(+), 18 deletions(-) diff --git a/src/main/kotlin/com/fasterxml/jackson/module/kotlin/Extensions.kt b/src/main/kotlin/com/fasterxml/jackson/module/kotlin/Extensions.kt index 9d715df02..7f4c3ffda 100644 --- a/src/main/kotlin/com/fasterxml/jackson/module/kotlin/Extensions.kt +++ b/src/main/kotlin/com/fasterxml/jackson/module/kotlin/Extensions.kt @@ -4,12 +4,12 @@ import com.fasterxml.jackson.core.JsonParser import com.fasterxml.jackson.core.TreeNode import com.fasterxml.jackson.core.type.TypeReference import com.fasterxml.jackson.databind.JsonDeserializer -import com.fasterxml.jackson.databind.JsonMappingException import com.fasterxml.jackson.databind.JsonNode import com.fasterxml.jackson.databind.JsonSerializer import com.fasterxml.jackson.databind.MappingIterator import com.fasterxml.jackson.databind.ObjectMapper import com.fasterxml.jackson.databind.ObjectReader +import com.fasterxml.jackson.databind.RuntimeJsonMappingException import com.fasterxml.jackson.databind.json.JsonMapper import com.fasterxml.jackson.databind.module.SimpleModule import com.fasterxml.jackson.databind.node.ArrayNode @@ -57,10 +57,11 @@ inline fun jacksonTypeRef(): TypeReference = object: TypeReferenc inline fun Any?.checkTypeMismatch(): T { // Basically, this check assumes that T is non-null and the value is null. // Since this can be caused by both input or ObjectMapper implementation errors, - // a more abstract JsonMappingException is thrown. + // a more abstract RuntimeJsonMappingException is thrown. if (this !is T) { - throw JsonMappingException( - null, + // Since the databind implementation of MappingIterator throws RuntimeJsonMappingException, + // JsonMappingException was not used to unify the behavior. + throw RuntimeJsonMappingException( "Deserialized value did not match the specified type; " + "specified ${T::class.qualifiedName} but was ${this?.let { it::class.qualifiedName }}" ) diff --git a/src/test/kotlin/com/fasterxml/jackson/module/kotlin/ReadValueTest.kt b/src/test/kotlin/com/fasterxml/jackson/module/kotlin/ReadValueTest.kt index 10424d5ee..777efbaec 100644 --- a/src/test/kotlin/com/fasterxml/jackson/module/kotlin/ReadValueTest.kt +++ b/src/test/kotlin/com/fasterxml/jackson/module/kotlin/ReadValueTest.kt @@ -1,6 +1,6 @@ package com.fasterxml.jackson.module.kotlin -import com.fasterxml.jackson.databind.JsonMappingException +import com.fasterxml.jackson.databind.RuntimeJsonMappingException import com.fasterxml.jackson.databind.node.NullNode import org.junit.jupiter.api.Nested import org.junit.jupiter.api.Test @@ -13,15 +13,15 @@ class ReadValueTest { @Test fun jsonParser() { val src = defaultMapper.createParser("null") - assertThrows { + assertThrows { defaultMapper.readValue(src) - } + }.printStackTrace() } @Test fun file() { val src = createTempJson("null") - assertThrows { + assertThrows { defaultMapper.readValue(src) } } @@ -34,7 +34,7 @@ class ReadValueTest { @Test fun string() { val src = "null" - assertThrows { + assertThrows { defaultMapper.readValue(src) } } @@ -42,7 +42,7 @@ class ReadValueTest { @Test fun reader() { val src = StringReader("null") - assertThrows { + assertThrows { defaultMapper.readValue(src) } } @@ -50,7 +50,7 @@ class ReadValueTest { @Test fun inputStream() { val src = "null".byteInputStream() - assertThrows { + assertThrows { defaultMapper.readValue(src) } } @@ -58,21 +58,21 @@ class ReadValueTest { @Test fun byteArray() { val src = "null".toByteArray() - assertThrows { + assertThrows { defaultMapper.readValue(src) } } @Test fun treeToValueTreeNode() { - assertThrows { + assertThrows { defaultMapper.treeToValue(NullNode.instance) } } @Test fun convertValueAny() { - assertThrows { + assertThrows { defaultMapper.convertValue(null) } } @@ -81,7 +81,7 @@ class ReadValueTest { fun readValueTypedJsonParser() { val reader = defaultMapper.reader() val src = reader.createParser("null") - assertThrows { + assertThrows { reader.readValueTyped(src) } } diff --git a/src/test/kotlin/com/fasterxml/jackson/module/kotlin/ReadValuesTest.kt b/src/test/kotlin/com/fasterxml/jackson/module/kotlin/ReadValuesTest.kt index 4e55cab39..2d2aa10b5 100644 --- a/src/test/kotlin/com/fasterxml/jackson/module/kotlin/ReadValuesTest.kt +++ b/src/test/kotlin/com/fasterxml/jackson/module/kotlin/ReadValuesTest.kt @@ -2,7 +2,6 @@ package com.fasterxml.jackson.module.kotlin import com.fasterxml.jackson.core.JsonParser import com.fasterxml.jackson.databind.DeserializationContext -import com.fasterxml.jackson.databind.JsonMappingException import com.fasterxml.jackson.databind.RuntimeJsonMappingException import com.fasterxml.jackson.databind.deser.std.StdDeserializer import com.fasterxml.jackson.databind.module.SimpleModule @@ -46,7 +45,7 @@ class ReadValuesTest { val itr = mapper.readValues(src) assertEquals("foo", itr.nextValue()) - assertThrows { + assertThrows { itr.nextValue() } } @@ -58,7 +57,7 @@ class ReadValuesTest { val itr = reader.readValuesTyped(src) assertEquals("foo", itr.next()) - assertThrows { + assertThrows { itr.next() } } From f4a5449251c9762fb00162f31398a823db00bf44 Mon Sep 17 00:00:00 2001 From: wrongwrong Date: Sun, 6 Apr 2025 15:38:45 +0900 Subject: [PATCH 7/9] Added nullability indication to error message --- .../kotlin/com/fasterxml/jackson/module/kotlin/Extensions.kt | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/main/kotlin/com/fasterxml/jackson/module/kotlin/Extensions.kt b/src/main/kotlin/com/fasterxml/jackson/module/kotlin/Extensions.kt index 7f4c3ffda..4618d2d38 100644 --- a/src/main/kotlin/com/fasterxml/jackson/module/kotlin/Extensions.kt +++ b/src/main/kotlin/com/fasterxml/jackson/module/kotlin/Extensions.kt @@ -59,11 +59,13 @@ inline fun Any?.checkTypeMismatch(): T { // Since this can be caused by both input or ObjectMapper implementation errors, // a more abstract RuntimeJsonMappingException is thrown. if (this !is T) { + val nullability = if (null is T) "?" else "(non-null)" + // Since the databind implementation of MappingIterator throws RuntimeJsonMappingException, // JsonMappingException was not used to unify the behavior. throw RuntimeJsonMappingException( "Deserialized value did not match the specified type; " + - "specified ${T::class.qualifiedName} but was ${this?.let { it::class.qualifiedName }}" + "specified ${T::class.qualifiedName}${nullability} but was ${this?.let { it::class.qualifiedName }}" ) } return this From 371a85800b238995a67ac9eb20be0178dd38dd3f Mon Sep 17 00:00:00 2001 From: wrongwrong Date: Sat, 12 Apr 2025 18:06:50 +0900 Subject: [PATCH 8/9] Add docs --- .../jackson/module/kotlin/Extensions.kt | 72 +++++++++++++++++++ 1 file changed, 72 insertions(+) diff --git a/src/main/kotlin/com/fasterxml/jackson/module/kotlin/Extensions.kt b/src/main/kotlin/com/fasterxml/jackson/module/kotlin/Extensions.kt index 4618d2d38..522694331 100644 --- a/src/main/kotlin/com/fasterxml/jackson/module/kotlin/Extensions.kt +++ b/src/main/kotlin/com/fasterxml/jackson/module/kotlin/Extensions.kt @@ -71,8 +71,20 @@ inline fun Any?.checkTypeMismatch(): T { return this } +/** + * Shorthand for [ObjectMapper.readValue]. + * @throws RuntimeJsonMappingException Especially if [T] is non-null and the value read is null. + * Other cases where the read value is of a different type than [T] + * due to an incorrect customization to [ObjectMapper]. + */ inline fun ObjectMapper.readValue(jp: JsonParser): T = readValue(jp, jacksonTypeRef()) .checkTypeMismatch() +/** + * Shorthand for [ObjectMapper.readValues]. + * @throws RuntimeJsonMappingException Especially if [T] is non-null and the value read is null. + * Other cases where the read value is of a different type than [T] + * due to an incorrect customization to [ObjectMapper]. + */ inline fun ObjectMapper.readValues(jp: JsonParser): MappingIterator { val values = readValues(jp, jacksonTypeRef()) @@ -81,23 +93,83 @@ inline fun ObjectMapper.readValues(jp: JsonParser): MappingIterator< } } +/** + * Shorthand for [ObjectMapper.readValue]. + * @throws RuntimeJsonMappingException Especially if [T] is non-null and the value read is null. + * Other cases where the read value is of a different type than [T] + * due to an incorrect customization to [ObjectMapper]. + */ inline fun ObjectMapper.readValue(src: File): T = readValue(src, jacksonTypeRef()).checkTypeMismatch() +/** + * Shorthand for [ObjectMapper.readValue]. + * @throws RuntimeJsonMappingException Especially if [T] is non-null and the value read is null. + * Other cases where the read value is of a different type than [T] + * due to an incorrect customization to [ObjectMapper]. + */ inline fun ObjectMapper.readValue(src: URL): T = readValue(src, jacksonTypeRef()).checkTypeMismatch() +/** + * Shorthand for [ObjectMapper.readValue]. + * @throws RuntimeJsonMappingException Especially if [T] is non-null and the value read is null. + * Other cases where the read value is of a different type than [T] + * due to an incorrect customization to [ObjectMapper]. + */ inline fun ObjectMapper.readValue(content: String): T = readValue(content, jacksonTypeRef()) .checkTypeMismatch() +/** + * Shorthand for [ObjectMapper.readValue]. + * @throws RuntimeJsonMappingException Especially if [T] is non-null and the value read is null. + * Other cases where the read value is of a different type than [T] + * due to an incorrect customization to [ObjectMapper]. + */ inline fun ObjectMapper.readValue(src: Reader): T = readValue(src, jacksonTypeRef()).checkTypeMismatch() +/** + * Shorthand for [ObjectMapper.readValue]. + * @throws RuntimeJsonMappingException Especially if [T] is non-null and the value read is null. + * Other cases where the read value is of a different type than [T] + * due to an incorrect customization to [ObjectMapper]. + */ inline fun ObjectMapper.readValue(src: InputStream): T = readValue(src, jacksonTypeRef()) .checkTypeMismatch() +/** + * Shorthand for [ObjectMapper.readValue]. + * @throws RuntimeJsonMappingException Especially if [T] is non-null and the value read is null. + * Other cases where the read value is of a different type than [T] + * due to an incorrect customization to [ObjectMapper]. + */ inline fun ObjectMapper.readValue(src: ByteArray): T = readValue(src, jacksonTypeRef()) .checkTypeMismatch() +/** + * Shorthand for [ObjectMapper.readValue]. + * @throws RuntimeJsonMappingException Especially if [T] is non-null and the value read is null. + * Other cases where the read value is of a different type than [T] + * due to an incorrect customization to [ObjectMapper]. + */ inline fun ObjectMapper.treeToValue(n: TreeNode): T = readValue(this.treeAsTokens(n), jacksonTypeRef()) .checkTypeMismatch() +/** + * Shorthand for [ObjectMapper.convertValue]. + * @throws RuntimeJsonMappingException Especially if [T] is non-null and the value read is null. + * Other cases where the read value is of a different type than [T] + * due to an incorrect customization to [ObjectMapper]. + */ inline fun ObjectMapper.convertValue(from: Any?): T = convertValue(from, jacksonTypeRef()) .checkTypeMismatch() +/** + * Shorthand for [ObjectMapper.readValue]. + * @throws RuntimeJsonMappingException Especially if [T] is non-null and the value read is null. + * Other cases where the read value is of a different type than [T] + * due to an incorrect customization to [ObjectMapper]. + */ inline fun ObjectReader.readValueTyped(jp: JsonParser): T = readValue(jp, jacksonTypeRef()) .checkTypeMismatch() +/** + * Shorthand for [ObjectMapper.readValues]. + * @throws RuntimeJsonMappingException Especially if [T] is non-null and the value read is null. + * Other cases where the read value is of a different type than [T] + * due to an incorrect customization to [ObjectMapper]. + */ inline fun ObjectReader.readValuesTyped(jp: JsonParser): Iterator { val values = readValues(jp, jacksonTypeRef()) From 0eb0cfcc303a10141577a94f0051771a836ad8ef Mon Sep 17 00:00:00 2001 From: wrongwrong Date: Sat, 12 Apr 2025 18:23:48 +0900 Subject: [PATCH 9/9] Update release notes wrt #937 --- release-notes/CREDITS-2.x | 3 +++ release-notes/VERSION-2.x | 8 ++++++++ 2 files changed, 11 insertions(+) diff --git a/release-notes/CREDITS-2.x b/release-notes/CREDITS-2.x index f0506727b..e5f19416a 100644 --- a/release-notes/CREDITS-2.x +++ b/release-notes/CREDITS-2.x @@ -17,6 +17,9 @@ Contributors: # 2.19.0 (not yet released) +WrongWrong (@k163377) +* #937: Added type match check to read functions + Tatu Saloranta (@cowtowncoder) * #889: Upgrade kotlin dep to 1.9.25 (from 1.9.24) diff --git a/release-notes/VERSION-2.x b/release-notes/VERSION-2.x index bdafec63d..7cd03965c 100644 --- a/release-notes/VERSION-2.x +++ b/release-notes/VERSION-2.x @@ -16,6 +16,14 @@ Co-maintainers: === Releases === ------------------------------------------------------------------------ +2.19.0 (not yet released) + +#937: For `readValue` and other shorthands for `ObjectMapper` deserialization methods, + type consistency checks have been added. + A `RuntimeJsonMappingException` will be thrown in case of inconsistency. + This fixes a problem that broke `Kotlin` null safety by reading null as a value even if the type parameter was specified as non-null. + It also checks for custom errors in ObjectMapper that cause a different value to be read than the specified type parameter. + 2.19.0-rc2 (07-Apr-2025) #929: Added consideration of `JsonProperty.isRequired` added in `2.19` in `hasRequiredMarker` processing.