@@ -4,9 +4,18 @@ import com.fasterxml.jackson.databind.BeanDescription
44import com.fasterxml.jackson.databind.DeserializationConfig
55import com.fasterxml.jackson.databind.DeserializationContext
66import com.fasterxml.jackson.databind.JavaType
7+ import com.fasterxml.jackson.databind.JsonMappingException
78import com.fasterxml.jackson.databind.KeyDeserializer
89import com.fasterxml.jackson.databind.deser.std.StdKeyDeserializer
10+ import com.fasterxml.jackson.databind.exc.InvalidDefinitionException
911import com.fasterxml.jackson.databind.module.SimpleKeyDeserializers
12+ import io.github.projectmapk.jackson.module.kogera.ReflectionCache
13+ import io.github.projectmapk.jackson.module.kogera.ValueClassBoxConverter
14+ import io.github.projectmapk.jackson.module.kogera.isUnboxableValueClass
15+ import io.github.projectmapk.jackson.module.kogera.toSignature
16+ import kotlinx.metadata.isSecondary
17+ import kotlinx.metadata.jvm.signature
18+ import java.lang.reflect.Method
1019import java.math.BigInteger
1120
1221// The reason why key is treated as nullable is to match the tentative behavior of StdKeyDeserializer.
@@ -40,18 +49,74 @@ internal object ULongKeyDeserializer : StdKeyDeserializer(-1, ULong::class.java)
4049 key?.let { ULongChecker .readWithRangeCheck(null , BigInteger (it)) }
4150}
4251
43- internal object KotlinKeyDeserializers : SimpleKeyDeserializers() {
44- private fun readResolve (): Any = KotlinKeyDeserializers
52+ // The implementation is designed to be compatible with various creators, just in case.
53+ internal class ValueClassKeyDeserializer <S , D : Any >(
54+ private val creator : Method ,
55+ private val converter : ValueClassBoxConverter <S , D >
56+ ) : KeyDeserializer() {
57+ private val unboxedClass: Class <* > = creator.parameterTypes[0 ]
4558
59+ init {
60+ creator.apply { if (! this .isAccessible) this .isAccessible = true }
61+ }
62+
63+ // Based on databind error
64+ // https://github.com/FasterXML/jackson-databind/blob/341f8d360a5f10b5e609d6ee0ea023bf597ce98a/src/main/java/com/fasterxml/jackson/databind/deser/DeserializerCache.java#L624
65+ private fun errorMessage (boxedType : JavaType ): String =
66+ " Could not find (Map) Key deserializer for types wrapped in $boxedType "
67+
68+ override fun deserializeKey (key : String? , ctxt : DeserializationContext ): Any {
69+ val unboxedJavaType = ctxt.constructType(unboxedClass)
70+
71+ return try {
72+ // findKeyDeserializer does not return null, and an exception will be thrown if not found.
73+ val value = ctxt.findKeyDeserializer(unboxedJavaType, null ).deserializeKey(key, ctxt)
74+ @Suppress(" UNCHECKED_CAST" )
75+ converter.convert(creator.invoke(null , value) as S )
76+ } catch (e: InvalidDefinitionException ) {
77+ throw JsonMappingException .from(ctxt, errorMessage(ctxt.constructType(converter.boxedClass)), e)
78+ }
79+ }
80+
81+ companion object {
82+ fun createOrNull (
83+ valueClass : Class <* >,
84+ cache : ReflectionCache
85+ ): ValueClassKeyDeserializer <* , * >? {
86+ val jmClass = cache.getJmClass(valueClass) ? : return null
87+ val primaryKmConstructorSignature =
88+ jmClass.constructors.first { ! it.isSecondary }.signature
89+
90+ // Only primary constructor is allowed as creator, regardless of visibility.
91+ // This is because it is based on the WrapsNullableValueClassBoxDeserializer.
92+ // Also, as far as I could research, there was no such functionality as JsonKeyCreator,
93+ // so it was not taken into account.
94+ return valueClass.declaredMethods.find { it.toSignature() == primaryKmConstructorSignature }?.let {
95+ val unboxedClass = it.returnType
96+
97+ val converter = cache.getValueClassBoxConverter(unboxedClass, valueClass)
98+
99+ ValueClassKeyDeserializer (it, converter)
100+ }
101+ }
102+ }
103+ }
104+
105+ internal class KotlinKeyDeserializers (private val cache : ReflectionCache ) : SimpleKeyDeserializers() {
46106 override fun findKeyDeserializer (
47107 type : JavaType ,
48108 config : DeserializationConfig ? ,
49109 beanDesc : BeanDescription ?
50- ): KeyDeserializer ? = when (type.rawClass) {
51- UByte ::class .java -> UByteKeyDeserializer
52- UShort ::class .java -> UShortKeyDeserializer
53- UInt ::class .java -> UIntKeyDeserializer
54- ULong ::class .java -> ULongKeyDeserializer
55- else -> null
110+ ): KeyDeserializer ? {
111+ val rawClass = type.rawClass
112+
113+ return when {
114+ rawClass == UByte ::class .java -> UByteKeyDeserializer
115+ rawClass == UShort ::class .java -> UShortKeyDeserializer
116+ rawClass == UInt ::class .java -> UIntKeyDeserializer
117+ rawClass == ULong ::class .java -> ULongKeyDeserializer
118+ rawClass.isUnboxableValueClass() -> ValueClassKeyDeserializer .createOrNull(rawClass, cache)
119+ else -> null
120+ }
56121 }
57122}
0 commit comments