Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Enforce nullability at deser time #405

Open
wants to merge 4 commits into
base: 2.12
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions release-notes/CREDITS-2.x
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,3 +20,5 @@ class MissingKotlinParameterException(val parameter: KParameter,
msg: String
) : this(parameter, processor as JsonParser, msg)
}

class NullInputException(msg: String) : MismatchedInputException(null, msg)
44 changes: 23 additions & 21 deletions src/main/kotlin/com/fasterxml/jackson/module/kotlin/Extensions.kt
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -36,22 +31,22 @@ fun ObjectMapper.registerKotlinModule(): ObjectMapper = this.registerModule(kotl

inline fun <reified T> jacksonTypeRef(): TypeReference<T> = object: TypeReference<T>() {}

inline fun <reified T> ObjectMapper.readValue(jp: JsonParser): T = readValue(jp, jacksonTypeRef<T>())
inline fun <reified T> ObjectMapper.readValues(jp: JsonParser): MappingIterator<T> = readValues(jp, jacksonTypeRef<T>())
inline fun <reified T> ObjectMapper.readValue(jp: JsonParser): T = readValue(jp, jacksonTypeRef<T>()).throwIfNullableTypeIsNull()
inline fun <reified T> ObjectMapper.readValues(jp: JsonParser): MappingIterator<T> = readValues(jp, jacksonTypeRef<T>()).throwIfNullableTypeIsNull()

inline fun <reified T> ObjectMapper.readValue(src: File): T = readValue(src, jacksonTypeRef<T>())
inline fun <reified T> ObjectMapper.readValue(src: URL): T = readValue(src, jacksonTypeRef<T>())
inline fun <reified T> ObjectMapper.readValue(content: String): T = readValue(content, jacksonTypeRef<T>())
inline fun <reified T> ObjectMapper.readValue(src: Reader): T = readValue(src, jacksonTypeRef<T>())
inline fun <reified T> ObjectMapper.readValue(src: InputStream): T = readValue(src, jacksonTypeRef<T>())
inline fun <reified T> ObjectMapper.readValue(src: ByteArray): T = readValue(src, jacksonTypeRef<T>())
inline fun <reified T> ObjectMapper.readValue(src: File): T = readValue(src, jacksonTypeRef<T>()).throwIfNullableTypeIsNull()
inline fun <reified T> ObjectMapper.readValue(src: URL): T = readValue(src, jacksonTypeRef<T>()).throwIfNullableTypeIsNull()
inline fun <reified T> ObjectMapper.readValue(content: String): T = readValue(content, jacksonTypeRef<T>()).throwIfNullableTypeIsNull()
inline fun <reified T> ObjectMapper.readValue(src: Reader): T = readValue(src, jacksonTypeRef<T>()).throwIfNullableTypeIsNull()
inline fun <reified T> ObjectMapper.readValue(src: InputStream): T = readValue(src, jacksonTypeRef<T>()).throwIfNullableTypeIsNull()
inline fun <reified T> ObjectMapper.readValue(src: ByteArray): T = readValue(src, jacksonTypeRef<T>()).throwIfNullableTypeIsNull()

inline fun <reified T> ObjectMapper.treeToValue(n: TreeNode): T? = treeToValue(n, T::class.java)
inline fun <reified T> ObjectMapper.convertValue(from: Any): T = convertValue(from, jacksonTypeRef<T>())
inline fun <reified T> ObjectMapper.treeToValue(n: TreeNode): T? = treeToValue(n, T::class.java).throwIfNullableTypeIsNull()
inline fun <reified T> ObjectMapper.convertValue(from: Any): T = convertValue(from, jacksonTypeRef<T>()).throwIfNullableTypeIsNull()

inline fun <reified T> ObjectReader.readValueTyped(jp: JsonParser): T = readValue(jp, jacksonTypeRef<T>())
inline fun <reified T> ObjectReader.readValuesTyped(jp: JsonParser): Iterator<T> = readValues(jp, jacksonTypeRef<T>())
inline fun <reified T> ObjectReader.treeToValue(n: TreeNode): T? = treeToValue(n, T::class.java)
inline fun <reified T> ObjectReader.readValueTyped(jp: JsonParser): T = readValue(jp, jacksonTypeRef<T>()).throwIfNullableTypeIsNull()
inline fun <reified T> ObjectReader.readValuesTyped(jp: JsonParser): Iterator<T> = readValues(jp, jacksonTypeRef<T>()).throwIfNullableTypeIsNull()
inline fun <reified T> 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)
Expand All @@ -64,4 +59,11 @@ inline fun <reified T : Any> SimpleModule.addSerializer(kClass: KClass<T>, seria
inline fun <reified T : Any> SimpleModule.addDeserializer(kClass: KClass<T>, deserializer: JsonDeserializer<T>) = this.apply {
addDeserializer(kClass.java, deserializer)
addDeserializer(kClass.javaObjectType, deserializer)
}
}

inline fun <reified T> T.throwIfNullableTypeIsNull(): T {
if (null !is T && this == null) {
throw NullInputException(msg = "Cannot deserialize null into non-null object of type ${T::class.java.simpleName}")
}
return this
}
Original file line number Diff line number Diff line change
Expand Up @@ -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.assertThrows
import org.junit.Test
import kotlin.test.assertEquals

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<BasicPerson>(json)
Expand All @@ -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<MyData> = mapper.readValue(jsonStr)
assertThat(myList, equalTo(listOf(MyData("value1", 1), MyData("value2", 2))))
}
}

enum class Options { ONE }

@Test
fun testNullEnumThrows() {
val nullInputException = assertThrows("foo", NullInputException::class.java) {
mapper.readValue<Options>("null")
}
assertEquals("Cannot deserialize null into non-null object of type Options", nullInputException.message)
}
}