From 772990613a0d0d2d9cf0ff88c48a31bb51037d3b Mon Sep 17 00:00:00 2001 From: wrongwrong Date: Sat, 19 Jun 2021 15:56:10 +0900 Subject: [PATCH 1/6] add serialization settings --- .../module/kotlin/KotlinSerializers.kt | 24 ++++++++++++++++++- 1 file changed, 23 insertions(+), 1 deletion(-) 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 ddad2736..0e1660df 100644 --- a/src/main/kotlin/com/fasterxml/jackson/module/kotlin/KotlinSerializers.kt +++ b/src/main/kotlin/com/fasterxml/jackson/module/kotlin/KotlinSerializers.kt @@ -6,6 +6,7 @@ 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) { @@ -40,6 +41,25 @@ object ULongSerializer : StdSerializer(ULong::class.java) { } } +object ValueClassUnboxSerializer : StdSerializer(Any::class.java) { + override fun serialize(value: Any, gen: JsonGenerator, provider: SerializerProvider) { + val unboxed = value::class.java.getMethod("unbox-impl").invoke(value) + + if (unboxed == null) { + gen.writeNull() + return + } + + 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") internal class KotlinSerializers : Serializers.Base() { override fun findSerializer( @@ -52,6 +72,8 @@ internal class KotlinSerializers : Serializers.Base() { UShort::class.java.isAssignableFrom(type.rawClass) -> UShortSerializer UInt::class.java.isAssignableFrom(type.rawClass) -> UIntSerializer ULong::class.java.isAssignableFrom(type.rawClass) -> ULongSerializer + // The priority of Unboxing needs to be lowered so as not to break the serialization of Unsigned Integers. + type.rawClass.isUnboxableValueClass() -> ValueClassUnboxSerializer else -> null } -} \ No newline at end of file +} From a869d0be481ba5ae783dcf0c4c57fe97195068f3 Mon Sep 17 00:00:00 2001 From: wrongwrong Date: Sat, 19 Jun 2021 18:18:36 +0900 Subject: [PATCH 2/6] add test --- .../module/kotlin/test/github/Github464.kt | 155 ++++++++++++++++++ 1 file changed, 155 insertions(+) create mode 100644 src/test/kotlin/com/fasterxml/jackson/module/kotlin/test/github/Github464.kt 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 new file mode 100644 index 00000000..71da92a5 --- /dev/null +++ b/src/test/kotlin/com/fasterxml/jackson/module/kotlin/test/github/Github464.kt @@ -0,0 +1,155 @@ +package com.fasterxml.jackson.module.kotlin.test.github + +import com.fasterxml.jackson.core.JsonGenerator +import com.fasterxml.jackson.databind.ObjectMapper +import com.fasterxml.jackson.databind.SerializerProvider +import com.fasterxml.jackson.databind.json.JsonMapper +import com.fasterxml.jackson.databind.module.SimpleModule +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 org.junit.Ignore +import org.junit.Test +import kotlin.test.assertEquals + +class Github464 { + class UnboxTest { + @JvmInline + value class ValueClass(val value: Int) + data class WrapperClass(val inlineField: ValueClass) + + class Poko( + val foo: ValueClass, + val bar: ValueClass?, + @JvmField + val baz: ValueClass, + val qux: Collection, + val quux: Array, + val corge: WrapperClass, + val grault: WrapperClass?, + val garply: Map, + val waldo: 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() + ) + + val om = jacksonObjectMapper() + assertEquals(""" + { + "foo": 0, + "bar": null, + "baz": 0, + "qux": [0, null], + "quux": [0, null], + "corge": { + "inlineField": 0 + }, + "grault": null, + "garply": {}, + "waldo": {} + } + """.replace("\\s".toRegex(), ""), + om.writeValueAsString(target) + ) + } + + @Ignore + @Test + fun test() { + val zeroValue = ValueClass(0) + val oneValue = ValueClass(1) + + 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), + waldo = mapOf(WrapperClass(zeroValue) to WrapperClass(zeroValue), WrapperClass(oneValue) to null) + ) + + val om = jacksonObjectMapper() + 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 + } + } + """.replace("\\s".toRegex(), ""), + om.writeValueAsString(target) + ) + } + } + + class SerializerPriorityTest { + @JvmInline + value class ValueBySerializer(val value: Int) + + object Serializer : StdSerializer(ValueBySerializer::class.java) { + override fun serialize(value: ValueBySerializer, gen: JsonGenerator, provider: SerializerProvider) { + gen.writeString(value.value.toString()) + } + } + + private val target = listOf(ValueBySerializer(1)) + + @Test + fun simpleTest() { + val sm = SimpleModule().addSerializer(Serializer) + val om: ObjectMapper = jacksonMapperBuilder().addModule(sm).build() + + assertEquals("""["1"]""", om.writeValueAsString(target)) + } + + // Currently, there is a situation where the serialization results are different depending on the registration order of the modules. + // This problem is not addressed because the serializer registered by the user has priority over Extensions.kt, + // since KotlinModule is basically registered first. + @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]""" + assertEquals(om1.writeValueAsString(target), om2.writeValueAsString(target)) + } + } +} From 954f57b874288fcaafd228c6c38dbabae93cda19 Mon Sep 17 00:00:00 2001 From: Drew Stephens Date: Wed, 23 Jun 2021 09:33:31 -0400 Subject: [PATCH 3/6] Use default pretty printer --- .../module/kotlin/test/github/Github464.kt | 70 +++++++++---------- 1 file changed, 35 insertions(+), 35 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 71da92a5..c02bda6e 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 @@ -15,6 +15,8 @@ import kotlin.test.assertEquals class Github464 { class UnboxTest { + val writer = jacksonObjectMapper().writerWithDefaultPrettyPrinter() + @JvmInline value class ValueClass(val value: Int) data class WrapperClass(val inlineField: ValueClass) @@ -49,23 +51,22 @@ class Github464 { waldo = emptyMap() ) - val om = jacksonObjectMapper() assertEquals(""" { - "foo": 0, - "bar": null, - "baz": 0, - "qux": [0, null], - "quux": [0, null], - "corge": { - "inlineField": 0 - }, - "grault": null, - "garply": {}, - "waldo": {} + "foo" : 0, + "bar" : null, + "baz" : 0, + "qux" : [ 0, null ], + "quux" : [ 0, null ], + "corge" : { + "inlineField" : 0 + }, + "grault" : null, + "garply" : { }, + "waldo" : { } } - """.replace("\\s".toRegex(), ""), - om.writeValueAsString(target) + """.trimIndent(), + writer.writeValueAsString(target) ) } @@ -87,31 +88,30 @@ class Github464 { waldo = mapOf(WrapperClass(zeroValue) to WrapperClass(zeroValue), WrapperClass(oneValue) to null) ) - val om = jacksonObjectMapper() assertEquals(""" { - "foo": 0, - "bar": null, - "baz": 0, - "qux": [0, null], - "quux": [0, null], - "corge": { - "inlineField": 0 - }, - "grault": null, - "garply": { - "0": 0, - "1": null + "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 }, - "waldo": { - "{inlineField=0}": { - "inlineField": 0 - }, - "{inlineField=1}": null - } + "{inlineField=1}" : null + } } - """.replace("\\s".toRegex(), ""), - om.writeValueAsString(target) + """.trimIndent(), + writer.writeValueAsString(target) ) } } From 30691b4115fa5a10dc65badb944dd9a2eae9d372 Mon Sep 17 00:00:00 2001 From: Drew Stephens Date: Wed, 23 Jun 2021 09:37:40 -0400 Subject: [PATCH 4/6] Expect failure rather than ignoring test --- .../jackson/module/kotlin/test/github/Github464.kt | 11 +++++++---- 1 file changed, 7 insertions(+), 4 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 c02bda6e..cda04445 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 @@ -9,6 +9,8 @@ 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 @@ -70,7 +72,6 @@ class Github464 { ) } - @Ignore @Test fun test() { val zeroValue = ValueClass(0) @@ -88,7 +89,8 @@ class Github464 { waldo = mapOf(WrapperClass(zeroValue) to WrapperClass(zeroValue), WrapperClass(oneValue) to null) ) - assertEquals(""" + expectFailure("GitHub #469 has been fixed!") { + assertEquals(""" { "foo" : 0, "bar" : null, @@ -111,8 +113,9 @@ class Github464 { } } """.trimIndent(), - writer.writeValueAsString(target) - ) + writer.writeValueAsString(target) + ) + } } } From 7276f5ee74040956fae01d17b2447f927f9b2cc3 Mon Sep 17 00:00:00 2001 From: Drew Stephens Date: Wed, 23 Jun 2021 09:38:24 -0400 Subject: [PATCH 5/6] Make writer private & explicitly typed --- .../fasterxml/jackson/module/kotlin/test/github/Github464.kt | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) 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 cda04445..77996510 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 @@ -2,6 +2,7 @@ package com.fasterxml.jackson.module.kotlin.test.github import com.fasterxml.jackson.core.JsonGenerator import com.fasterxml.jackson.databind.ObjectMapper +import com.fasterxml.jackson.databind.ObjectWriter import com.fasterxml.jackson.databind.SerializerProvider import com.fasterxml.jackson.databind.json.JsonMapper import com.fasterxml.jackson.databind.module.SimpleModule @@ -17,7 +18,7 @@ import kotlin.test.assertEquals class Github464 { class UnboxTest { - val writer = jacksonObjectMapper().writerWithDefaultPrettyPrinter() + private val writer: ObjectWriter = jacksonObjectMapper().writerWithDefaultPrettyPrinter() @JvmInline value class ValueClass(val value: Int) From bf79fda32d10fbea31f21f9eef580d9bfd7a1ebb Mon Sep 17 00:00:00 2001 From: Drew Stephens Date: Wed, 23 Jun 2021 09:43:10 -0400 Subject: [PATCH 6/6] Add credit for value classes support --- 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 468019cd..eba9d00f 100644 --- a/release-notes/CREDITS-2.x +++ b/release-notes/CREDITS-2.x @@ -13,6 +13,10 @@ Authors: Contributors: +wrongwrong (k163377@github) +* #468: Improved support for value classes + (2.13) + Christopher Mason (masoncj@github) * #194: Contributed test case for @JsonIdentityInfo usage (2.12.NEXT)