From c24010aec4e3ed831722ba1383b797079d91a60f Mon Sep 17 00:00:00 2001 From: wrongwrong Date: Sat, 19 Jun 2021 23:35:55 +0900 Subject: [PATCH 1/6] move method --- .../com/fasterxml/jackson/module/kotlin/Extensions.kt | 6 ++++++ .../fasterxml/jackson/module/kotlin/KotlinSerializers.kt | 7 ------- 2 files changed, 6 insertions(+), 7 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 4caf3b3be..613dd5882 100644 --- a/src/main/kotlin/com/fasterxml/jackson/module/kotlin/Extensions.kt +++ b/src/main/kotlin/com/fasterxml/jackson/module/kotlin/Extensions.kt @@ -80,3 +80,9 @@ internal fun Int.toBitSet(): BitSet { } return bits } + +// In the future, value class without JvmInline will be available, and unbox may not be able to handle it. +// https://github.com/FasterXML/jackson-module-kotlin/issues/464 +// The JvmInline annotation can be given to Java class, +// so the isKotlinClass decision is necessary (the order is preferable in terms of possible frequency). +internal fun Class<*>.isUnboxableValueClass() = annotations.any { it is JvmInline } && this.isKotlinClass() diff --git a/src/main/kotlin/com/fasterxml/jackson/module/kotlin/KotlinSerializers.kt b/src/main/kotlin/com/fasterxml/jackson/module/kotlin/KotlinSerializers.kt index 0e1660df0..db5666686 100644 --- a/src/main/kotlin/com/fasterxml/jackson/module/kotlin/KotlinSerializers.kt +++ b/src/main/kotlin/com/fasterxml/jackson/module/kotlin/KotlinSerializers.kt @@ -6,7 +6,6 @@ import com.fasterxml.jackson.core.JsonGenerator import com.fasterxml.jackson.databind.* import com.fasterxml.jackson.databind.ser.Serializers import com.fasterxml.jackson.databind.ser.std.StdSerializer -import com.fasterxml.jackson.module.kotlin.ValueClassUnboxSerializer.isUnboxableValueClass import java.math.BigInteger object SequenceSerializer : StdSerializer>(Sequence::class.java) { @@ -52,12 +51,6 @@ object ValueClassUnboxSerializer : StdSerializer(Any::class.java) { provider.findValueSerializer(unboxed::class.java).serialize(unboxed, gen, provider) } - - // In the future, value class without JvmInline will be available, and unbox may not be able to handle it. - // https://github.com/FasterXML/jackson-module-kotlin/issues/464 - // The JvmInline annotation can be given to Java class, - // so the isKotlinClass decision is necessary (the order is preferable in terms of possible frequency). - fun Class<*>.isUnboxableValueClass() = annotations.any { it is JvmInline } && this.isKotlinClass() } @Suppress("EXPERIMENTAL_API_USAGE") From f8e8d82e8f4cafbe86657a7e1506fdd6a50b3836 Mon Sep 17 00:00:00 2001 From: wrongwrong Date: Sun, 20 Jun 2021 00:46:45 +0900 Subject: [PATCH 2/6] add unbox key serializer --- .../module/kotlin/KotlinKeySerializers.kt | 33 +++++++++++++++++++ .../jackson/module/kotlin/KotlinModule.kt | 1 + 2 files changed, 34 insertions(+) create mode 100644 src/main/kotlin/com/fasterxml/jackson/module/kotlin/KotlinKeySerializers.kt diff --git a/src/main/kotlin/com/fasterxml/jackson/module/kotlin/KotlinKeySerializers.kt b/src/main/kotlin/com/fasterxml/jackson/module/kotlin/KotlinKeySerializers.kt new file mode 100644 index 000000000..43a36a116 --- /dev/null +++ b/src/main/kotlin/com/fasterxml/jackson/module/kotlin/KotlinKeySerializers.kt @@ -0,0 +1,33 @@ +package com.fasterxml.jackson.module.kotlin + +import com.fasterxml.jackson.core.JsonGenerator +import com.fasterxml.jackson.databind.* +import com.fasterxml.jackson.databind.ser.Serializers +import com.fasterxml.jackson.databind.ser.std.StdSerializer +import com.fasterxml.jackson.databind.type.TypeFactory + +internal object ValueClassUnboxKeySerializer : StdSerializer(Any::class.java) { + override fun serialize(value: Any, gen: JsonGenerator, provider: SerializerProvider) { + val method = value::class.java.getMethod("unbox-impl") + val unboxed = method.invoke(value) + + if (unboxed == null) { + val javaType = provider.typeFactory.constructType(method.genericReturnType) + provider.findNullKeySerializer(javaType, null).serialize(null, gen, provider) + return + } + + provider.findKeySerializer(unboxed::class.java, null).serialize(unboxed, gen, provider) + } +} + +class KotlinKeySerializers : Serializers.Base() { + override fun findSerializer( + config: SerializationConfig, + type: JavaType, + beanDesc: BeanDescription + ): JsonSerializer<*>? = when { + type.rawClass.isUnboxableValueClass() -> ValueClassUnboxKeySerializer + else -> null + } +} diff --git a/src/main/kotlin/com/fasterxml/jackson/module/kotlin/KotlinModule.kt b/src/main/kotlin/com/fasterxml/jackson/module/kotlin/KotlinModule.kt index 4a9f54d99..176453b1a 100644 --- a/src/main/kotlin/com/fasterxml/jackson/module/kotlin/KotlinModule.kt +++ b/src/main/kotlin/com/fasterxml/jackson/module/kotlin/KotlinModule.kt @@ -123,6 +123,7 @@ class KotlinModule @Deprecated( context.addDeserializers(KotlinDeserializers()) context.addSerializers(KotlinSerializers()) + context.addKeySerializers(KotlinKeySerializers()) fun addMixIn(clazz: Class<*>, mixin: Class<*>) { context.setMixInAnnotations(clazz, mixin) From 8c2d8252223582056479891c54e04f038b45e582 Mon Sep 17 00:00:00 2001 From: wrongwrong Date: Thu, 24 Jun 2021 14:53:32 +0900 Subject: [PATCH 3/6] fix and add tests --- .../module/kotlin/test/github/Github464.kt | 125 +++++++----------- 1 file changed, 48 insertions(+), 77 deletions(-) diff --git a/src/test/kotlin/com/fasterxml/jackson/module/kotlin/test/github/Github464.kt b/src/test/kotlin/com/fasterxml/jackson/module/kotlin/test/github/Github464.kt index 779965109..309102fd2 100644 --- a/src/test/kotlin/com/fasterxml/jackson/module/kotlin/test/github/Github464.kt +++ b/src/test/kotlin/com/fasterxml/jackson/module/kotlin/test/github/Github464.kt @@ -1,6 +1,7 @@ package com.fasterxml.jackson.module.kotlin.test.github import com.fasterxml.jackson.core.JsonGenerator +import com.fasterxml.jackson.databind.JsonSerializer import com.fasterxml.jackson.databind.ObjectMapper import com.fasterxml.jackson.databind.ObjectWriter import com.fasterxml.jackson.databind.SerializerProvider @@ -10,18 +11,25 @@ import com.fasterxml.jackson.databind.ser.std.StdSerializer import com.fasterxml.jackson.module.kotlin.KotlinModule import com.fasterxml.jackson.module.kotlin.jacksonMapperBuilder import com.fasterxml.jackson.module.kotlin.jacksonObjectMapper -import com.fasterxml.jackson.module.kotlin.test.expectFailure -import org.junit.ComparisonFailure import org.junit.Ignore import org.junit.Test import kotlin.test.assertEquals class Github464 { class UnboxTest { - private val writer: ObjectWriter = jacksonObjectMapper().writerWithDefaultPrettyPrinter() + object NullValueClassKeySerializer : StdSerializer(ValueClass::class.java) { + override fun serialize(value: ValueClass?, gen: JsonGenerator, provider: SerializerProvider) { + gen.writeFieldName("null-key") + } + } + + @Suppress("UNCHECKED_CAST") + private val writer: ObjectWriter = jacksonObjectMapper() + .apply { serializerProvider.setNullKeySerializer(NullValueClassKeySerializer as JsonSerializer) } + .writerWithDefaultPrettyPrinter() @JvmInline - value class ValueClass(val value: Int) + value class ValueClass(val value: Int?) data class WrapperClass(val inlineField: ValueClass) class Poko( @@ -33,50 +41,14 @@ class Github464 { val quux: Array, val corge: WrapperClass, val grault: WrapperClass?, - val garply: Map, - val waldo: Map + val garply: Map ) - // TODO: Remove this function after applying unbox to key of Map and cancel Ignore of test. - @Test - fun tempTest() { - val zeroValue = ValueClass(0) - - val target = Poko( - foo = zeroValue, - bar = null, - baz = zeroValue, - qux = listOf(zeroValue, null), - quux = arrayOf(zeroValue, null), - corge = WrapperClass(zeroValue), - grault = null, - garply = emptyMap(), - waldo = emptyMap() - ) - - assertEquals(""" - { - "foo" : 0, - "bar" : null, - "baz" : 0, - "qux" : [ 0, null ], - "quux" : [ 0, null ], - "corge" : { - "inlineField" : 0 - }, - "grault" : null, - "garply" : { }, - "waldo" : { } - } - """.trimIndent(), - writer.writeValueAsString(target) - ) - } - @Test fun test() { val zeroValue = ValueClass(0) val oneValue = ValueClass(1) + val nullValue = ValueClass(null) val target = Poko( foo = zeroValue, @@ -86,37 +58,30 @@ class Github464 { quux = arrayOf(zeroValue, null), corge = WrapperClass(zeroValue), grault = null, - garply = mapOf(zeroValue to zeroValue, oneValue to null), - waldo = mapOf(WrapperClass(zeroValue) to WrapperClass(zeroValue), WrapperClass(oneValue) to null) + garply = mapOf(zeroValue to zeroValue, oneValue to null, nullValue to nullValue) ) - expectFailure("GitHub #469 has been fixed!") { - assertEquals(""" - { - "foo" : 0, - "bar" : null, - "baz" : 0, - "qux" : [ 0, null ], - "quux" : [ 0, null ], - "corge" : { - "inlineField" : 0 - }, - "grault" : null, - "garply" : { - "0" : 0, - "1" : null - }, - "waldo" : { - "{inlineField=0}" : { - "inlineField" : 0 - }, - "{inlineField=1}" : null - } - } - """.trimIndent(), - writer.writeValueAsString(target) - ) - } + assertEquals( + """ + { + "foo" : 0, + "bar" : null, + "baz" : 0, + "qux" : [ 0, null ], + "quux" : [ 0, null ], + "corge" : { + "inlineField" : 0 + }, + "grault" : null, + "garply" : { + "0" : 0, + "1" : null, + "null-key" : null + } + } + """.trimIndent(), + writer.writeValueAsString(target) + ) } } @@ -129,15 +94,22 @@ class Github464 { gen.writeString(value.value.toString()) } } + object KeySerializer : StdSerializer(ValueBySerializer::class.java) { + override fun serialize(value: ValueBySerializer, gen: JsonGenerator, provider: SerializerProvider) { + gen.writeFieldName(value.value.toString()) + } + } - private val target = listOf(ValueBySerializer(1)) + private val target = mapOf(ValueBySerializer(1) to ValueBySerializer(2)) + private val sm = SimpleModule() + .addSerializer(Serializer) + .addKeySerializer(ValueBySerializer::class.java, KeySerializer) @Test fun simpleTest() { - val sm = SimpleModule().addSerializer(Serializer) val om: ObjectMapper = jacksonMapperBuilder().addModule(sm).build() - assertEquals("""["1"]""", om.writeValueAsString(target)) + assertEquals("""{"1":"2"}""", om.writeValueAsString(target)) } // Currently, there is a situation where the serialization results are different depending on the registration order of the modules. @@ -146,13 +118,12 @@ class Github464 { @Ignore @Test fun priorityTest() { - val sm = SimpleModule().addSerializer(Serializer) val km = KotlinModule.Builder().build() val om1: ObjectMapper = JsonMapper.builder().addModules(km, sm).build() val om2: ObjectMapper = JsonMapper.builder().addModules(sm, km).build() - // om1(collect) -> """["1"]""" - // om2(broken) -> """[1]""" + // om1(collect) -> """{"1":"2"}""" + // om2(broken) -> """{"1":2}""" assertEquals(om1.writeValueAsString(target), om2.writeValueAsString(target)) } } From c102f3617fabffac79bf6a8452171e0cef66240f Mon Sep 17 00:00:00 2001 From: wrongwrong Date: Thu, 24 Jun 2021 15:20:07 +0900 Subject: [PATCH 4/6] Fixed an issue where NullValueSerializer was not being applied. --- .../module/kotlin/KotlinSerializers.kt | 2 +- .../module/kotlin/test/github/Github464.kt | 76 ++++++++++++++----- 2 files changed, 58 insertions(+), 20 deletions(-) diff --git a/src/main/kotlin/com/fasterxml/jackson/module/kotlin/KotlinSerializers.kt b/src/main/kotlin/com/fasterxml/jackson/module/kotlin/KotlinSerializers.kt index db5666686..7168217cd 100644 --- a/src/main/kotlin/com/fasterxml/jackson/module/kotlin/KotlinSerializers.kt +++ b/src/main/kotlin/com/fasterxml/jackson/module/kotlin/KotlinSerializers.kt @@ -45,7 +45,7 @@ object ValueClassUnboxSerializer : StdSerializer(Any::class.java) { val unboxed = value::class.java.getMethod("unbox-impl").invoke(value) if (unboxed == null) { - gen.writeNull() + provider.findNullValueSerializer(null).serialize(unboxed, gen, provider) return } diff --git a/src/test/kotlin/com/fasterxml/jackson/module/kotlin/test/github/Github464.kt b/src/test/kotlin/com/fasterxml/jackson/module/kotlin/test/github/Github464.kt index 309102fd2..551df6caf 100644 --- a/src/test/kotlin/com/fasterxml/jackson/module/kotlin/test/github/Github464.kt +++ b/src/test/kotlin/com/fasterxml/jackson/module/kotlin/test/github/Github464.kt @@ -23,11 +23,6 @@ class Github464 { } } - @Suppress("UNCHECKED_CAST") - private val writer: ObjectWriter = jacksonObjectMapper() - .apply { serializerProvider.setNullKeySerializer(NullValueClassKeySerializer as JsonSerializer) } - .writerWithDefaultPrettyPrinter() - @JvmInline value class ValueClass(val value: Int?) data class WrapperClass(val inlineField: ValueClass) @@ -44,22 +39,27 @@ class Github464 { val garply: Map ) + private val zeroValue = ValueClass(0) + private val oneValue = ValueClass(1) + private val nullValue = ValueClass(null) + + private val target = Poko( + foo = zeroValue, + bar = null, + baz = zeroValue, + qux = listOf(zeroValue, null), + quux = arrayOf(zeroValue, null), + corge = WrapperClass(zeroValue), + grault = null, + garply = mapOf(zeroValue to zeroValue, oneValue to null, nullValue to nullValue) + ) + @Test fun test() { - val zeroValue = ValueClass(0) - val oneValue = ValueClass(1) - val nullValue = ValueClass(null) - - val target = Poko( - foo = zeroValue, - bar = null, - baz = zeroValue, - qux = listOf(zeroValue, null), - quux = arrayOf(zeroValue, null), - corge = WrapperClass(zeroValue), - grault = null, - garply = mapOf(zeroValue to zeroValue, oneValue to null, nullValue to nullValue) - ) + @Suppress("UNCHECKED_CAST") + val writer: ObjectWriter = jacksonObjectMapper() + .apply { serializerProvider.setNullKeySerializer(NullValueClassKeySerializer as JsonSerializer) } + .writerWithDefaultPrettyPrinter() assertEquals( """ @@ -83,6 +83,44 @@ class Github464 { writer.writeValueAsString(target) ) } + + object NullValueSerializer : StdSerializer(Any::class.java) { + override fun serialize(value: Any?, gen: JsonGenerator, provider: SerializerProvider) { + gen.writeString("null-value") + } + } + + @Test + fun nullValueSerializerTest() { + @Suppress("UNCHECKED_CAST") + val writer = jacksonObjectMapper() + .apply { + serializerProvider.setNullKeySerializer(NullValueClassKeySerializer as JsonSerializer) + serializerProvider.setNullValueSerializer(NullValueSerializer) + }.writerWithDefaultPrettyPrinter() + + assertEquals( + """ + { + "foo" : 0, + "bar" : "null-value", + "baz" : 0, + "qux" : [ 0, "null-value" ], + "quux" : [ 0, "null-value" ], + "corge" : { + "inlineField" : 0 + }, + "grault" : "null-value", + "garply" : { + "0" : 0, + "1" : "null-value", + "null-key" : "null-value" + } + } + """.trimIndent(), + writer.writeValueAsString(target) + ) + } } class SerializerPriorityTest { From f5433e8302eeb65b535886da6254e583eedd342c Mon Sep 17 00:00:00 2001 From: wrongwrong Date: Thu, 24 Jun 2021 15:44:55 +0900 Subject: [PATCH 5/6] fixes --- .../fasterxml/jackson/module/kotlin/KotlinKeySerializers.kt | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/main/kotlin/com/fasterxml/jackson/module/kotlin/KotlinKeySerializers.kt b/src/main/kotlin/com/fasterxml/jackson/module/kotlin/KotlinKeySerializers.kt index 43a36a116..2db5694fc 100644 --- a/src/main/kotlin/com/fasterxml/jackson/module/kotlin/KotlinKeySerializers.kt +++ b/src/main/kotlin/com/fasterxml/jackson/module/kotlin/KotlinKeySerializers.kt @@ -4,7 +4,6 @@ import com.fasterxml.jackson.core.JsonGenerator import com.fasterxml.jackson.databind.* import com.fasterxml.jackson.databind.ser.Serializers import com.fasterxml.jackson.databind.ser.std.StdSerializer -import com.fasterxml.jackson.databind.type.TypeFactory internal object ValueClassUnboxKeySerializer : StdSerializer(Any::class.java) { override fun serialize(value: Any, gen: JsonGenerator, provider: SerializerProvider) { @@ -21,7 +20,7 @@ internal object ValueClassUnboxKeySerializer : StdSerializer(Any::class.jav } } -class KotlinKeySerializers : Serializers.Base() { +internal class KotlinKeySerializers : Serializers.Base() { override fun findSerializer( config: SerializationConfig, type: JavaType, From e5169e8026bfa1ce061c5e890843e8cc5bb0c1b1 Mon Sep 17 00:00:00 2001 From: Drew Stephens Date: Tue, 28 Sep 2021 15:45:55 -0400 Subject: [PATCH 6/6] Copyedit comment --- .../kotlin/com/fasterxml/jackson/module/kotlin/Extensions.kt | 4 ++-- 1 file changed, 2 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 3467e8a2c..8e283c0e5 100644 --- a/src/main/kotlin/com/fasterxml/jackson/module/kotlin/Extensions.kt +++ b/src/main/kotlin/com/fasterxml/jackson/module/kotlin/Extensions.kt @@ -115,8 +115,8 @@ internal fun Int.toBitSet(): BitSet { return bits } -// In the future, value class without JvmInline will be available, and unbox may not be able to handle it. +// In the future, value classes without @JvmInline will be available, and unboxing may not be able to handle it. // https://github.com/FasterXML/jackson-module-kotlin/issues/464 -// The JvmInline annotation can be given to Java class, +// The JvmInline annotation can be added to Java classes, // so the isKotlinClass decision is necessary (the order is preferable in terms of possible frequency). internal fun Class<*>.isUnboxableValueClass() = annotations.any { it is JvmInline } && this.isKotlinClass()