From 1b3edc32af82d440ee46c9860b804c3065e5ffe8 Mon Sep 17 00:00:00 2001 From: Rafal Bednarczuk Date: Fri, 25 Sep 2020 08:14:12 +0200 Subject: [PATCH 1/4] fix #373 --- .../jackson/module/kotlin/Extensions.kt | 33 +++++++++++-------- 1 file changed, 20 insertions(+), 13 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 8178c2dc..16bbe507 100644 --- a/src/main/kotlin/com/fasterxml/jackson/module/kotlin/Extensions.kt +++ b/src/main/kotlin/com/fasterxml/jackson/module/kotlin/Extensions.kt @@ -36,22 +36,22 @@ fun ObjectMapper.registerKotlinModule(): ObjectMapper = this.registerModule(kotl inline fun jacksonTypeRef(): TypeReference = object: TypeReference() {} -inline fun ObjectMapper.readValue(jp: JsonParser): T = readValue(jp, jacksonTypeRef()) -inline fun ObjectMapper.readValues(jp: JsonParser): MappingIterator = readValues(jp, jacksonTypeRef()) +inline fun ObjectMapper.readValue(jp: JsonParser): T = readValue(jp, jacksonTypeRef()).throwIfNullableTypeIsNull() +inline fun ObjectMapper.readValues(jp: JsonParser): MappingIterator = readValues(jp, jacksonTypeRef()).throwIfNullableTypeIsNull() -inline fun ObjectMapper.readValue(src: File): T = readValue(src, jacksonTypeRef()) -inline fun ObjectMapper.readValue(src: URL): T = readValue(src, jacksonTypeRef()) -inline fun ObjectMapper.readValue(content: String): T = readValue(content, jacksonTypeRef()) -inline fun ObjectMapper.readValue(src: Reader): T = readValue(src, jacksonTypeRef()) -inline fun ObjectMapper.readValue(src: InputStream): T = readValue(src, jacksonTypeRef()) -inline fun ObjectMapper.readValue(src: ByteArray): T = readValue(src, jacksonTypeRef()) +inline fun ObjectMapper.readValue(src: File): T = readValue(src, jacksonTypeRef()).throwIfNullableTypeIsNull() +inline fun ObjectMapper.readValue(src: URL): T = readValue(src, jacksonTypeRef()).throwIfNullableTypeIsNull() +inline fun ObjectMapper.readValue(content: String): T = readValue(content, jacksonTypeRef()).throwIfNullableTypeIsNull() +inline fun ObjectMapper.readValue(src: Reader): T = readValue(src, jacksonTypeRef()).throwIfNullableTypeIsNull() +inline fun ObjectMapper.readValue(src: InputStream): T = readValue(src, jacksonTypeRef()).throwIfNullableTypeIsNull() +inline fun ObjectMapper.readValue(src: ByteArray): T = readValue(src, jacksonTypeRef()).throwIfNullableTypeIsNull() -inline fun ObjectMapper.treeToValue(n: TreeNode): T? = treeToValue(n, T::class.java) -inline fun ObjectMapper.convertValue(from: Any): T = convertValue(from, jacksonTypeRef()) +inline fun ObjectMapper.treeToValue(n: TreeNode): T? = treeToValue(n, T::class.java).throwIfNullableTypeIsNull() +inline fun ObjectMapper.convertValue(from: Any): T = convertValue(from, jacksonTypeRef()).throwIfNullableTypeIsNull() -inline fun ObjectReader.readValueTyped(jp: JsonParser): T = readValue(jp, jacksonTypeRef()) -inline fun ObjectReader.readValuesTyped(jp: JsonParser): Iterator = readValues(jp, jacksonTypeRef()) -inline fun ObjectReader.treeToValue(n: TreeNode): T? = treeToValue(n, T::class.java) +inline fun ObjectReader.readValueTyped(jp: JsonParser): T = readValue(jp, jacksonTypeRef()).throwIfNullableTypeIsNull() +inline fun ObjectReader.readValuesTyped(jp: JsonParser): Iterator = readValues(jp, jacksonTypeRef()).throwIfNullableTypeIsNull() +inline fun ObjectReader.treeToValue(n: TreeNode): T? = treeToValue(n, T::class.java).throwIfNullableTypeIsNull() internal fun JsonMappingException.wrapWithPath(refFrom: Any?, refFieldName: String) = JsonMappingException.wrapWithPath(this, refFrom, refFieldName) internal fun JsonMappingException.wrapWithPath(refFrom: Any?, index: Int) = JsonMappingException.wrapWithPath(this, refFrom, index) @@ -64,4 +64,11 @@ inline fun SimpleModule.addSerializer(kClass: KClass, seria inline fun SimpleModule.addDeserializer(kClass: KClass, deserializer: JsonDeserializer) = this.apply { addDeserializer(kClass.java, deserializer) addDeserializer(kClass.javaObjectType, deserializer) +} + +inline fun T.throwIfNullableTypeIsNull(): T { + if (null !is T && this == null) { + throw Exception() + } + return this } \ No newline at end of file From f43e56527913ed1e191450bd78b9eca7923d7461 Mon Sep 17 00:00:00 2001 From: Drew Stephens Date: Fri, 1 Jan 2021 14:30:05 -0500 Subject: [PATCH 2/4] Create NullInputException and test for null input deser --- .../jackson/module/kotlin/Exceptions.kt | 2 ++ .../jackson/module/kotlin/Extensions.kt | 13 +++----- .../kotlin/test/ExtensionMethodsTests.kt | 31 +++++++++++++++---- 3 files changed, 31 insertions(+), 15 deletions(-) diff --git a/src/main/kotlin/com/fasterxml/jackson/module/kotlin/Exceptions.kt b/src/main/kotlin/com/fasterxml/jackson/module/kotlin/Exceptions.kt index d52aa122..7537c389 100644 --- a/src/main/kotlin/com/fasterxml/jackson/module/kotlin/Exceptions.kt +++ b/src/main/kotlin/com/fasterxml/jackson/module/kotlin/Exceptions.kt @@ -20,3 +20,5 @@ class MissingKotlinParameterException(val parameter: KParameter, msg: String ) : this(parameter, processor as JsonParser, msg) } + +class NullInputException(msg: String) : MismatchedInputException(null, msg) 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 16bbe507..429ee57a 100644 --- a/src/main/kotlin/com/fasterxml/jackson/module/kotlin/Extensions.kt +++ b/src/main/kotlin/com/fasterxml/jackson/module/kotlin/Extensions.kt @@ -3,14 +3,9 @@ package com.fasterxml.jackson.module.kotlin import com.fasterxml.jackson.core.JsonParser import com.fasterxml.jackson.core.TreeNode import com.fasterxml.jackson.core.type.TypeReference -import com.fasterxml.jackson.databind.JsonMappingException -import com.fasterxml.jackson.databind.MappingIterator -import com.fasterxml.jackson.databind.ObjectMapper -import com.fasterxml.jackson.databind.ObjectReader -import com.fasterxml.jackson.databind.module.SimpleModule -import com.fasterxml.jackson.databind.JsonDeserializer -import com.fasterxml.jackson.databind.JsonSerializer +import com.fasterxml.jackson.databind.* import com.fasterxml.jackson.databind.json.JsonMapper +import com.fasterxml.jackson.databind.module.SimpleModule import java.io.File import java.io.InputStream import java.io.Reader @@ -68,7 +63,7 @@ inline fun SimpleModule.addDeserializer(kClass: KClass, des inline fun T.throwIfNullableTypeIsNull(): T { if (null !is T && this == null) { - throw Exception() + throw NullInputException(msg = "Cannot deserialize null into non-null object of type ${T::class.java.name}") } return this -} \ No newline at end of file +} diff --git a/src/test/kotlin/com/fasterxml/jackson/module/kotlin/test/ExtensionMethodsTests.kt b/src/test/kotlin/com/fasterxml/jackson/module/kotlin/test/ExtensionMethodsTests.kt index a5cb3c53..810f62f4 100644 --- a/src/test/kotlin/com/fasterxml/jackson/module/kotlin/test/ExtensionMethodsTests.kt +++ b/src/test/kotlin/com/fasterxml/jackson/module/kotlin/test/ExtensionMethodsTests.kt @@ -2,17 +2,22 @@ package com.fasterxml.jackson.module.kotlin.test import com.fasterxml.jackson.databind.ObjectMapper import com.fasterxml.jackson.databind.SerializationFeature -import com.fasterxml.jackson.module.kotlin.* +import com.fasterxml.jackson.module.kotlin.NullInputException +import com.fasterxml.jackson.module.kotlin.jacksonObjectMapper +import com.fasterxml.jackson.module.kotlin.readValue import org.hamcrest.CoreMatchers.equalTo import org.hamcrest.MatcherAssert.assertThat +import org.junit.Assert.assertNull +import org.junit.Assert.assertThrows import org.junit.Test class TestExtensionMethods { val mapper: ObjectMapper = jacksonObjectMapper().configure(SerializationFeature.INDENT_OUTPUT, false) - data class BasicPerson(val name: String, val age: Int) + @Test + fun testAllInferenceForms() { + data class BasicPerson(val name: String, val age: Int) - @Test fun testAllInferenceForms() { val json = """{"name":"John Smith","age":30}""" val inferRightSide = mapper.readValue(json) @@ -26,11 +31,25 @@ class TestExtensionMethods { assertThat(person, equalTo(expectedPerson)) } - data class MyData(val a: String, val b: Int) + /** + * https://stackoverflow.com/questions/33368328/how-to-use-jackson-to-deserialize-to-kotlin-collections + */ + @Test + fun testStackOverflow33368328() { + data class MyData(val a: String, val b: Int) - @Test fun testStackOverflow33368328() { val jsonStr = """[{"a": "value1", "b": 1}, {"a": "value2", "b": 2}]""" val myList: List = mapper.readValue(jsonStr) assertThat(myList, equalTo(listOf(MyData("value1", 1), MyData("value2", 2)))) } -} \ No newline at end of file + + enum class Options { ONE, TWO } + + @Test + fun testNullEnumThrows() { + assertThrows(NullInputException::class.java) { + val foo: Options = mapper.readValue("null") + assertNull(foo) + } + } +} From a4c8529921441b75711494f410711a04e709b6dc Mon Sep 17 00:00:00 2001 From: Drew Stephens Date: Fri, 1 Jan 2021 14:41:20 -0500 Subject: [PATCH 3/4] Assert thrown message; use short name --- .../com/fasterxml/jackson/module/kotlin/Extensions.kt | 2 +- .../module/kotlin/test/ExtensionMethodsTests.kt | 10 +++++----- 2 files changed, 6 insertions(+), 6 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 429ee57a..03b8c40e 100644 --- a/src/main/kotlin/com/fasterxml/jackson/module/kotlin/Extensions.kt +++ b/src/main/kotlin/com/fasterxml/jackson/module/kotlin/Extensions.kt @@ -63,7 +63,7 @@ inline fun SimpleModule.addDeserializer(kClass: KClass, des inline fun T.throwIfNullableTypeIsNull(): T { if (null !is T && this == null) { - throw NullInputException(msg = "Cannot deserialize null into non-null object of type ${T::class.java.name}") + throw NullInputException(msg = "Cannot deserialize null into non-null object of type ${T::class.java.simpleName}") } return this } diff --git a/src/test/kotlin/com/fasterxml/jackson/module/kotlin/test/ExtensionMethodsTests.kt b/src/test/kotlin/com/fasterxml/jackson/module/kotlin/test/ExtensionMethodsTests.kt index 810f62f4..0e315896 100644 --- a/src/test/kotlin/com/fasterxml/jackson/module/kotlin/test/ExtensionMethodsTests.kt +++ b/src/test/kotlin/com/fasterxml/jackson/module/kotlin/test/ExtensionMethodsTests.kt @@ -7,9 +7,9 @@ import com.fasterxml.jackson.module.kotlin.jacksonObjectMapper import com.fasterxml.jackson.module.kotlin.readValue import org.hamcrest.CoreMatchers.equalTo import org.hamcrest.MatcherAssert.assertThat -import org.junit.Assert.assertNull import org.junit.Assert.assertThrows import org.junit.Test +import kotlin.test.assertEquals class TestExtensionMethods { val mapper: ObjectMapper = jacksonObjectMapper().configure(SerializationFeature.INDENT_OUTPUT, false) @@ -43,13 +43,13 @@ class TestExtensionMethods { assertThat(myList, equalTo(listOf(MyData("value1", 1), MyData("value2", 2)))) } - enum class Options { ONE, TWO } + enum class Options { ONE } @Test fun testNullEnumThrows() { - assertThrows(NullInputException::class.java) { - val foo: Options = mapper.readValue("null") - assertNull(foo) + val nullInputException = assertThrows("foo", NullInputException::class.java) { + mapper.readValue("null") } + assertEquals("Cannot deserialize null into non-null object of type Options", nullInputException.message) } } From 5829f36fddc8146ca19b59c9d608500f646fa117 Mon Sep 17 00:00:00 2001 From: Drew Stephens Date: Fri, 1 Jan 2021 14:45:05 -0500 Subject: [PATCH 4/4] Add credit for #373 --- release-notes/CREDITS-2.x | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/release-notes/CREDITS-2.x b/release-notes/CREDITS-2.x index de041b8c..2387f69b 100644 --- a/release-notes/CREDITS-2.x +++ b/release-notes/CREDITS-2.x @@ -13,6 +13,10 @@ Authors: Contributors: +(rafalbednarczuk@github) +* Prevent null values from being deserialized into non-null types (#373) + (2.12.1) + Wolfgang Jung (elektro-wolle@github) * Fixed inline class serialization (2.12.1)