Skip to content
Merged
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
7 changes: 7 additions & 0 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -208,6 +208,13 @@
TODO Remove after release of 2.14 and update the oldVersion above to 2.14.
-->
<exclude>com.fasterxml.jackson.module.kotlin.ValueClassBoxSerializer</exclude>
<exclude>com.fasterxml.jackson.module.kotlin.ValueClassSerializer$StaticJsonValue</exclude>
<exclude>com.fasterxml.jackson.module.kotlin.ValueClassSerializer$Unbox</exclude>
<exclude>com.fasterxml.jackson.module.kotlin.ValueClassStaticJsonValueSerializer</exclude>
<exclude>com.fasterxml.jackson.module.kotlin.ValueClassUnboxSerializer.serialize(java.lang.Object, com.fasterxml.jackson.core.JsonGenerator, com.fasterxml.jackson.databind.SerializerProvider)</exclude>
<exclude>com.fasterxml.jackson.databind.jsonschema.SchemaAware[com.fasterxml.jackson.databind.jsonschema.SchemaAware]</exclude>
<exclude>com.fasterxml.jackson.databind.jsonFormatVisitors.JsonFormatVisitable[com.fasterxml.jackson.databind.jsonFormatVisitors.JsonFormatVisitable]:INTERFACE_REMOVED, java.io.Serializable[java.io.Serializable]</exclude>
<exclude>com.fasterxml.jackson.module.kotlin.ValueClassUnboxSerializer</exclude>
</excludes>
</parameter>
</configuration>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import com.fasterxml.jackson.databind.Module
import com.fasterxml.jackson.databind.cfg.MapperConfig
import com.fasterxml.jackson.databind.introspect.*
import com.fasterxml.jackson.databind.jsontype.NamedType
import com.fasterxml.jackson.databind.ser.std.StdSerializer
import java.lang.reflect.AccessibleObject
import java.lang.reflect.Constructor
import java.lang.reflect.Field
Expand Down Expand Up @@ -62,7 +63,7 @@ internal class KotlinAnnotationIntrospector(private val context: Module.SetupCon
}

// Find a serializer to handle the case where the getter returns an unboxed value from the value class.
override fun findSerializer(am: Annotated): ValueClassBoxSerializer<*>? = when (am) {
override fun findSerializer(am: Annotated): StdSerializer<*>? = when (am) {
is AnnotatedMethod -> {
val getter = am.member.apply {
// If the return value of the getter is a value class,
Expand All @@ -87,8 +88,10 @@ internal class KotlinAnnotationIntrospector(private val context: Module.SetupCon
?.takeIf { it.isValue }
?.java
?.let { outerClazz ->
@Suppress("UNCHECKED_CAST")
ValueClassBoxSerializer(outerClazz, getter.returnType)
val innerClazz = getter.returnType

ValueClassStaticJsonValueSerializer.createdOrNull(outerClazz, innerClazz)
?: @Suppress("UNCHECKED_CAST") ValueClassBoxSerializer(outerClazz, innerClazz)
}
}
// Ignore the case of AnnotatedField, because JvmField cannot be set in the field of value class.
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package com.fasterxml.jackson.module.kotlin

import com.fasterxml.jackson.annotation.JsonValue
import com.fasterxml.jackson.core.JsonGenerator
import com.fasterxml.jackson.databind.BeanDescription
import com.fasterxml.jackson.databind.JavaType
Expand All @@ -8,8 +9,9 @@ import com.fasterxml.jackson.databind.SerializationConfig
import com.fasterxml.jackson.databind.SerializerProvider
import com.fasterxml.jackson.databind.ser.Serializers
import com.fasterxml.jackson.databind.ser.std.StdSerializer
import java.lang.reflect.Method
import java.lang.reflect.Modifier
import java.math.BigInteger
import kotlin.reflect.KClass

object SequenceSerializer : StdSerializer<Sequence<*>>(Sequence::class.java) {
override fun serialize(value: Sequence<*>, gen: JsonGenerator, provider: SerializerProvider) {
Expand Down Expand Up @@ -43,16 +45,47 @@ object ULongSerializer : StdSerializer<ULong>(ULong::class.java) {
}
}

object ValueClassUnboxSerializer : StdSerializer<Any>(Any::class.java) {
override fun serialize(value: Any, gen: JsonGenerator, provider: SerializerProvider) {
val unboxed = value::class.java.getMethod("unbox-impl").invoke(value)
// Class must be UnboxableValueClass.
private fun Class<*>.getStaticJsonValueGetter(): Method? = this.declaredMethods
.find { method -> Modifier.isStatic(method.modifiers) && method.annotations.any { it is JsonValue } }

if (unboxed == null) {
provider.findNullValueSerializer(null).serialize(unboxed, gen, provider)
return
internal sealed class ValueClassSerializer<T : Any>(t: Class<T>) : StdSerializer<T>(t) {
object Unbox : ValueClassSerializer<Any>(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) {
provider.findNullValueSerializer(null).serialize(unboxed, gen, provider)
return
}

provider.findValueSerializer(unboxed::class.java).serialize(unboxed, gen, provider)
}
}

class StaticJsonValue<T : Any>(
t: Class<T>, private val staticJsonValueGetter: Method
) : ValueClassSerializer<T>(t) {
private val unboxMethod: Method = t.getMethod("unbox-impl")

override fun serialize(value: T, gen: JsonGenerator, provider: SerializerProvider) {
val unboxed = unboxMethod.invoke(value)
// As shown in the processing of the factory function, jsonValueGetter is always a static method.
val jsonValue: Any? = staticJsonValueGetter.invoke(null, unboxed)
jsonValue
?.let { provider.findValueSerializer(it::class.java).serialize(it, gen, provider) }
?: provider.findNullValueSerializer(null).serialize(null, gen, provider)
}
}

provider.findValueSerializer(unboxed::class.java).serialize(unboxed, gen, provider)
companion object {
// `t` must be UnboxableValueClass.
// If create a function with a JsonValue in the value class,
// it will be compiled as a static method (= cannot be processed properly by Jackson),
// so use a ValueClassSerializer.StaticJsonValue to handle this.
fun from(t: Class<*>): ValueClassSerializer<*> = t.getStaticJsonValueGetter()
?.let { StaticJsonValue(t, it) }
?: Unbox
}
}

Expand All @@ -61,15 +94,19 @@ internal class KotlinSerializers : Serializers.Base() {
config: SerializationConfig?,
type: JavaType,
beanDesc: BeanDescription?
): JsonSerializer<*>? = when {
Sequence::class.java.isAssignableFrom(type.rawClass) -> SequenceSerializer
UByte::class.java.isAssignableFrom(type.rawClass) -> UByteSerializer
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
): JsonSerializer<*>? {
val rawClass = type.rawClass

return when {
Sequence::class.java.isAssignableFrom(rawClass) -> SequenceSerializer
UByte::class.java.isAssignableFrom(rawClass) -> UByteSerializer
UShort::class.java.isAssignableFrom(rawClass) -> UShortSerializer
UInt::class.java.isAssignableFrom(rawClass) -> UIntSerializer
ULong::class.java.isAssignableFrom(rawClass) -> ULongSerializer
// The priority of Unboxing needs to be lowered so as not to break the serialization of Unsigned Integers.
rawClass.isUnboxableValueClass() -> ValueClassSerializer.from(rawClass)
else -> null
}
}
}

Expand All @@ -89,3 +126,27 @@ internal class ValueClassBoxSerializer<T : Any>(
provider.findValueSerializer(outerClazz).serialize(boxed, gen, provider)
}
}

internal class ValueClassStaticJsonValueSerializer<T> private constructor(
innerClazz: Class<T>,
private val staticJsonValueGetter: Method
) : StdSerializer<T>(innerClazz) {
override fun serialize(value: T?, gen: JsonGenerator, provider: SerializerProvider) {
// As shown in the processing of the factory function, jsonValueGetter is always a static method.
val jsonValue: Any? = staticJsonValueGetter.invoke(null, value)
jsonValue
?.let { provider.findValueSerializer(it::class.java).serialize(it, gen, provider) }
?: provider.findNullValueSerializer(null).serialize(null, gen, provider)
}

// Since JsonValue can be processed correctly if it is given to a non-static getter/field,
// this class will only process if it is a `static` method.
companion object {
fun <T> createdOrNull(
outerClazz: Class<out Any>,
innerClazz: Class<T>
): ValueClassStaticJsonValueSerializer<T>? = outerClazz
.getStaticJsonValueGetter()
?.let { ValueClassStaticJsonValueSerializer(innerClazz, it) }
}
}
Loading