Skip to content

Commit

Permalink
Merge pull request #69 from ProjectMapK/fix-strict-null-check
Browse files Browse the repository at this point in the history
Fixed problem with deserialization failing for correct collections when StrictNullCkecks is enabled.
  • Loading branch information
k163377 authored Feb 14, 2023
2 parents 097c3dc + 43923ee commit 6a10b1d
Show file tree
Hide file tree
Showing 3 changed files with 98 additions and 8 deletions.
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package com.fasterxml.jackson.module.kotlin.annotation_introspector

import com.fasterxml.jackson.annotation.JsonProperty
import com.fasterxml.jackson.databind.JavaType
import com.fasterxml.jackson.databind.JsonSerializer
import com.fasterxml.jackson.databind.introspect.Annotated
import com.fasterxml.jackson.databind.introspect.AnnotatedMember
Expand Down Expand Up @@ -101,7 +102,7 @@ internal class KotlinFallbackAnnotationIntrospector(

valueParameter.createValueClassUnboxConverterOrNull(rawType) ?: run {
if (strictNullChecks) {
valueParameter.createStrictNullChecksConverterOrNull(rawType)
valueParameter.createStrictNullChecksConverterOrNull(a.type, rawType)
} else {
null
}
Expand Down Expand Up @@ -137,15 +138,15 @@ private fun ValueParameter.createValueClassUnboxConverterOrNull(rawType: Class<*
// @see com.fasterxml.jackson.module.kotlin._ported.test.StrictNullChecksTest#testListOfGenericWithNullValue
private fun ValueParameter.isNullishTypeAt(index: Int) = arguments.getOrNull(index)?.isNullable ?: true

private fun ValueParameter.createStrictNullChecksConverterOrNull(rawType: Class<*>): Converter<*, *>? {
private fun ValueParameter.createStrictNullChecksConverterOrNull(type: JavaType, rawType: Class<*>): Converter<*, *>? {
@Suppress("UNCHECKED_CAST")
return when {
Array::class.java.isAssignableFrom(rawType) && !this.isNullishTypeAt(0) ->
CollectionValueStrictNullChecksConverter.ForArray(this)
CollectionValueStrictNullChecksConverter.ForArray(type, this)
Iterable::class.java.isAssignableFrom(rawType) && !this.isNullishTypeAt(0) ->
CollectionValueStrictNullChecksConverter.ForIterable(this)
CollectionValueStrictNullChecksConverter.ForIterable(type, this)
Map::class.java.isAssignableFrom(rawType) && !this.isNullishTypeAt(1) ->
MapValueStrictNullChecksConverter(this)
MapValueStrictNullChecksConverter(type, this)
else -> null
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package com.fasterxml.jackson.module.kotlin.deser

import com.fasterxml.jackson.databind.JavaType
import com.fasterxml.jackson.databind.type.TypeFactory
import com.fasterxml.jackson.databind.util.Converter
import com.fasterxml.jackson.databind.util.StdConverter
import com.fasterxml.jackson.module.kotlin.MissingKotlinParameterException
import com.fasterxml.jackson.module.kotlin.deser.value_instantiator.creator.ValueParameter
Expand All @@ -16,7 +17,8 @@ internal class ValueClassUnboxConverter<T : Any>(private val valueClass: Class<T
override fun getInputType(typeFactory: TypeFactory): JavaType = typeFactory.constructType(valueClass)
}

internal sealed class CollectionValueStrictNullChecksConverter<T : Any> : StdConverter<T, T>() {
internal sealed class CollectionValueStrictNullChecksConverter<T : Any> : Converter<T, T> {
protected abstract val type: JavaType
protected abstract val valueParameter: ValueParameter

protected abstract fun getValues(value: T): Iterator<*>
Expand All @@ -35,22 +37,28 @@ internal sealed class CollectionValueStrictNullChecksConverter<T : Any> : StdCon
return value
}

override fun getInputType(typeFactory: TypeFactory): JavaType = type
override fun getOutputType(typeFactory: TypeFactory): JavaType = type

class ForIterable(
override val type: JavaType,
override val valueParameter: ValueParameter
) : CollectionValueStrictNullChecksConverter<Iterable<*>>() {
override fun getValues(value: Iterable<*>): Iterator<*> = value.iterator()
}

class ForArray(
class ForArray constructor(
override val type: JavaType,
override val valueParameter: ValueParameter
) : CollectionValueStrictNullChecksConverter<Array<*>>() {
override fun getValues(value: Array<*>): Iterator<*> = value.iterator()
}
}

internal class MapValueStrictNullChecksConverter(
private val type: JavaType,
private val valueParameter: ValueParameter
) : StdConverter<Map<*, *>, Map<*, *>>() {
) : Converter<Map<*, *>, Map<*, *>> {
override fun convert(value: Map<*, *>): Map<*, *> = value.apply {
entries.forEach { (k, v) ->
if (v == null) {
Expand All @@ -62,4 +70,7 @@ internal class MapValueStrictNullChecksConverter(
}
}
}

override fun getInputType(typeFactory: TypeFactory): JavaType = type
override fun getOutputType(typeFactory: TypeFactory): JavaType = type
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
package com.fasterxml.jackson.module.kotlin._integration.deser

import com.fasterxml.jackson.databind.ObjectMapper
import com.fasterxml.jackson.module.kotlin.KotlinFeature
import com.fasterxml.jackson.module.kotlin.KotlinModule
import com.fasterxml.jackson.module.kotlin.MissingKotlinParameterException
import com.fasterxml.jackson.module.kotlin.readValue
import org.junit.jupiter.api.Assertions.assertArrayEquals
import org.junit.jupiter.api.Assertions.assertEquals
import org.junit.jupiter.api.Nested
import org.junit.jupiter.api.Test
import org.junit.jupiter.api.assertThrows

class StrictNullChecksTest {
val mapper: ObjectMapper = ObjectMapper()
.registerModule(
KotlinModule.Builder()
.enable(KotlinFeature.StrictNullChecks)
.build()
)

class ArrayWrapper(val value: Array<Int>)
data class ListWrapper(val value: List<Int>)
data class MapWrapper(val value: Map<String, Int>)

@Nested
inner class NonNullInput {
@Test
fun array() {
val expected = ArrayWrapper(arrayOf(1))
val src = mapper.writeValueAsString(expected)
val result = mapper.readValue<ArrayWrapper>(src)

assertArrayEquals(expected.value, result.value)
}

@Test
fun list() {
val expected = ListWrapper(listOf(1))
val src = mapper.writeValueAsString(expected)
val result = mapper.readValue<ListWrapper>(src)

assertEquals(expected, result)
}

@Test
fun map() {
val expected = MapWrapper(mapOf("foo" to 1))
val src = mapper.writeValueAsString(expected)
val result = mapper.readValue<MapWrapper>(src)

assertEquals(expected, result)
}
}

data class AnyWrapper(val value: Any)

@Nested
inner class NullInput {
@Test
fun array() {
val src = mapper.writeValueAsString(AnyWrapper(arrayOf<Int?>(null)))
assertThrows<MissingKotlinParameterException> { mapper.readValue<ArrayWrapper>(src) }
}

@Test
fun list() {
val src = mapper.writeValueAsString(AnyWrapper(arrayOf<Int?>(null)))
assertThrows<MissingKotlinParameterException> { mapper.readValue<ListWrapper>(src) }
}

@Test
fun map() {
val src = mapper.writeValueAsString(AnyWrapper(mapOf("foo" to null)))
assertThrows<MissingKotlinParameterException> { mapper.readValue<MapWrapper>(src) }
}
}
}

0 comments on commit 6a10b1d

Please sign in to comment.