Skip to content

Commit af165b9

Browse files
authored
Merge pull request #391 from ProjectMapK/develop
Release 2025-09-27 02:18:47 +0000
2 parents 33869a1 + b9e068f commit af165b9

File tree

11 files changed

+216
-26
lines changed

11 files changed

+216
-26
lines changed

.github/workflows/test-main.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@ jobs:
4242
# LTS versions, latest version (if exists)
4343
java-version: [ '17', '21', '24' ]
4444
# Minimum version, latest release version, latest pre-release version (if exists)
45-
kotlin: ['2.0.21', '2.1.21', '2.2.10']
45+
kotlin: ['2.0.21', '2.1.21', '2.2.20']
4646
env:
4747
KOTLIN_VERSION: ${{ matrix.kotlin }}
4848
name: "Kotlin ${{ matrix.kotlin }} - Java ${{ matrix.java-version }}"

build.gradle.kts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ val jacksonVersion = libs.versions.jackson.get()
1818
val generatedSrcPath = "${layout.buildDirectory.get()}/generated/kotlin"
1919

2020
group = groupStr
21-
version = "${jacksonVersion}-beta29"
21+
version = "${jacksonVersion}-beta30"
2222

2323
repositories {
2424
mavenCentral()

src/main/kotlin/io/github/projectmapk/jackson/module/kogera/Converters.kt

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -127,7 +127,7 @@ internal class IntValueClassUnboxConverter<T : Any>(
127127
unboxMethod: Method,
128128
) : ValueClassUnboxConverter<T, Int>() {
129129
override val unboxedType: Type get() = INT_CLASS
130-
override val unboxHandle: MethodHandle = unreflectAsType(unboxMethod, ANY_TO_INT_METHOD_TYPE)
130+
override val unboxHandle: MethodHandle = unreflectAsTypeWithAccessibilityModification(unboxMethod, ANY_TO_INT_METHOD_TYPE)
131131

132132
override fun convert(value: T): Int = unboxHandle.invokeExact(value) as Int
133133
}
@@ -137,7 +137,7 @@ internal class LongValueClassUnboxConverter<T : Any>(
137137
unboxMethod: Method,
138138
) : ValueClassUnboxConverter<T, Long>() {
139139
override val unboxedType: Type get() = LONG_CLASS
140-
override val unboxHandle: MethodHandle = unreflectAsType(unboxMethod, ANY_TO_LONG_METHOD_TYPE)
140+
override val unboxHandle: MethodHandle = unreflectAsTypeWithAccessibilityModification(unboxMethod, ANY_TO_LONG_METHOD_TYPE)
141141

142142
override fun convert(value: T): Long = unboxHandle.invokeExact(value) as Long
143143
}
@@ -147,7 +147,7 @@ internal class StringValueClassUnboxConverter<T : Any>(
147147
unboxMethod: Method,
148148
) : ValueClassUnboxConverter<T, String?>() {
149149
override val unboxedType: Type get() = STRING_CLASS
150-
override val unboxHandle: MethodHandle = unreflectAsType(unboxMethod, ANY_TO_STRING_METHOD_TYPE)
150+
override val unboxHandle: MethodHandle = unreflectAsTypeWithAccessibilityModification(unboxMethod, ANY_TO_STRING_METHOD_TYPE)
151151

152152
override fun convert(value: T): String? = unboxHandle.invokeExact(value) as String?
153153
}
@@ -157,7 +157,7 @@ internal class JavaUuidValueClassUnboxConverter<T : Any>(
157157
unboxMethod: Method,
158158
) : ValueClassUnboxConverter<T, UUID?>() {
159159
override val unboxedType: Type get() = JAVA_UUID_CLASS
160-
override val unboxHandle: MethodHandle = unreflectAsType(unboxMethod, ANY_TO_JAVA_UUID_METHOD_TYPE)
160+
override val unboxHandle: MethodHandle = unreflectAsTypeWithAccessibilityModification(unboxMethod, ANY_TO_JAVA_UUID_METHOD_TYPE)
161161

162162
override fun convert(value: T): UUID? = unboxHandle.invokeExact(value) as UUID?
163163
}
@@ -167,7 +167,7 @@ internal class GenericValueClassUnboxConverter<T : Any>(
167167
override val unboxedType: Type,
168168
unboxMethod: Method,
169169
) : ValueClassUnboxConverter<T, Any?>() {
170-
override val unboxHandle: MethodHandle = unreflectAsType(unboxMethod, ANY_TO_ANY_METHOD_TYPE)
170+
override val unboxHandle: MethodHandle = unreflectAsTypeWithAccessibilityModification(unboxMethod, ANY_TO_ANY_METHOD_TYPE)
171171

172172
override fun convert(value: T): Any? = unboxHandle.invokeExact(value)
173173
}

src/main/kotlin/io/github/projectmapk/jackson/module/kogera/InternalCommons.kt

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import com.fasterxml.jackson.annotation.JsonCreator
44
import com.fasterxml.jackson.annotation.JsonProperty
55
import com.fasterxml.jackson.core.JsonParser
66
import com.fasterxml.jackson.databind.PropertyName
7+
import com.fasterxml.jackson.databind.util.ClassUtil
78
import io.github.projectmapk.jackson.module.kogera.annotation.JsonKUnbox
89
import java.lang.invoke.MethodHandle
910
import java.lang.invoke.MethodHandles
@@ -83,8 +84,13 @@ internal val LONG_TO_ANY_METHOD_TYPE by lazy { MethodType.methodType(ANY_CLASS,
8384
internal val STRING_TO_ANY_METHOD_TYPE by lazy { MethodType.methodType(ANY_CLASS, STRING_CLASS) }
8485
internal val JAVA_UUID_TO_ANY_METHOD_TYPE by lazy { MethodType.methodType(ANY_CLASS, JAVA_UUID_CLASS) }
8586

86-
internal fun unreflect(method: Method): MethodHandle = MethodHandles.lookup().unreflect(method)
87-
internal fun unreflectAsType(method: Method, type: MethodType): MethodHandle = unreflect(method).asType(type)
87+
internal fun unreflectWithAccessibilityModification(method: Method): MethodHandle = MethodHandles.lookup().unreflect(
88+
method.apply { ClassUtil.checkAndFixAccess(this, false) },
89+
)
90+
internal fun unreflectAsTypeWithAccessibilityModification(
91+
method: Method,
92+
type: MethodType,
93+
): MethodHandle = unreflectWithAccessibilityModification(method).asType(type)
8894

8995
private val primitiveClassToDesc = mapOf(
9096
Byte::class.java to 'B',

src/main/kotlin/io/github/projectmapk/jackson/module/kogera/deser/deserializers/KotlinDeserializers.kt

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -29,8 +29,8 @@ import io.github.projectmapk.jackson.module.kogera.hasCreatorAnnotation
2929
import io.github.projectmapk.jackson.module.kogera.isUnboxableValueClass
3030
import io.github.projectmapk.jackson.module.kogera.jmClass.JmClass
3131
import io.github.projectmapk.jackson.module.kogera.toSignature
32-
import io.github.projectmapk.jackson.module.kogera.unreflect
33-
import io.github.projectmapk.jackson.module.kogera.unreflectAsType
32+
import io.github.projectmapk.jackson.module.kogera.unreflectAsTypeWithAccessibilityModification
33+
import io.github.projectmapk.jackson.module.kogera.unreflectWithAccessibilityModification
3434
import java.lang.invoke.MethodHandle
3535
import java.lang.invoke.MethodHandles
3636
import java.lang.reflect.Method
@@ -110,7 +110,7 @@ internal sealed class NoConversionCreatorBoxDeserializer<S, D : Any>(
110110
) : WrapsNullableValueClassDeserializer<D>(converter.boxedClass) {
111111
protected abstract val inputType: Class<*>
112112
protected val handle: MethodHandle = MethodHandles
113-
.filterReturnValue(unreflect(creator), converter.boxHandle)
113+
.filterReturnValue(unreflectWithAccessibilityModification(creator), converter.boxHandle)
114114

115115
// Since the input to handle must be strict, invoke should be implemented in each class
116116
protected abstract fun invokeExact(value: S): D
@@ -191,7 +191,7 @@ internal class HasConversionCreatorWrapsSpecifiedBoxDeserializer<S, D : Any>(
191191
private val handle: MethodHandle
192192

193193
init {
194-
val unreflect = unreflect(creator).run {
194+
val unreflect = unreflectWithAccessibilityModification(creator).run {
195195
asType(type().changeParameterType(0, ANY_CLASS))
196196
}
197197
handle = MethodHandles.filterReturnValue(unreflect, converter.boxHandle)
@@ -223,7 +223,7 @@ internal class WrapsAnyValueClassBoxDeserializer<S, D : Any>(
223223
private val handle: MethodHandle
224224

225225
init {
226-
val unreflect = unreflectAsType(creator, ANY_TO_ANY_METHOD_TYPE)
226+
val unreflect = unreflectAsTypeWithAccessibilityModification(creator, ANY_TO_ANY_METHOD_TYPE)
227227
handle = MethodHandles.filterReturnValue(unreflect, converter.boxHandle)
228228
}
229229

src/main/kotlin/io/github/projectmapk/jackson/module/kogera/deser/deserializers/KotlinKeyDeserializers.kt

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -23,8 +23,8 @@ import io.github.projectmapk.jackson.module.kogera.StringValueClassBoxConverter
2323
import io.github.projectmapk.jackson.module.kogera.ValueClassBoxConverter
2424
import io.github.projectmapk.jackson.module.kogera.isUnboxableValueClass
2525
import io.github.projectmapk.jackson.module.kogera.toSignature
26-
import io.github.projectmapk.jackson.module.kogera.unreflect
27-
import io.github.projectmapk.jackson.module.kogera.unreflectAsType
26+
import io.github.projectmapk.jackson.module.kogera.unreflectAsTypeWithAccessibilityModification
27+
import io.github.projectmapk.jackson.module.kogera.unreflectWithAccessibilityModification
2828
import java.lang.invoke.MethodHandle
2929
import java.lang.invoke.MethodHandles
3030
import java.lang.reflect.Method
@@ -101,7 +101,7 @@ internal sealed class ValueClassKeyDeserializer<S, D : Any>(
101101
// Currently, only the primary constructor can be the creator of a key, so for specified types,
102102
// the return type of the primary constructor and the input type of the box function are exactly the same.
103103
// Therefore, performance is improved by omitting the asType call.
104-
unreflect(creator),
104+
unreflectWithAccessibilityModification(creator),
105105
)
106106

107107
internal class WrapsInt<D : Any>(
@@ -149,7 +149,7 @@ internal sealed class ValueClassKeyDeserializer<S, D : Any>(
149149
creator: Method,
150150
) : ValueClassKeyDeserializer<S, D>(
151151
converter,
152-
unreflectAsType(creator, ANY_TO_ANY_METHOD_TYPE),
152+
unreflectAsTypeWithAccessibilityModification(creator, ANY_TO_ANY_METHOD_TYPE),
153153
) {
154154
override val unboxedClass: Class<*> = creator.returnType
155155

src/main/kotlin/io/github/projectmapk/jackson/module/kogera/ser/serializers/KotlinKeySerializers.kt

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ import io.github.projectmapk.jackson.module.kogera.STRING_TO_ANY_METHOD_TYPE
2222
import io.github.projectmapk.jackson.module.kogera.StringValueClassUnboxConverter
2323
import io.github.projectmapk.jackson.module.kogera.ValueClassUnboxConverter
2424
import io.github.projectmapk.jackson.module.kogera.isUnboxableValueClass
25-
import io.github.projectmapk.jackson.module.kogera.unreflectAsType
25+
import io.github.projectmapk.jackson.module.kogera.unreflectAsTypeWithAccessibilityModification
2626
import java.lang.invoke.MethodHandle
2727
import java.lang.invoke.MethodHandles
2828
import java.lang.invoke.MethodType
@@ -51,7 +51,7 @@ internal sealed class ValueClassStaticJsonKeySerializer<T : Any>(
5151
methodType: MethodType,
5252
) : StdSerializer<T>(converter.valueClass) {
5353
private val keyType: Class<*> = staticJsonValueGetter.returnType
54-
private val handle: MethodHandle = unreflectAsType(staticJsonValueGetter, methodType).let {
54+
private val handle: MethodHandle = unreflectAsTypeWithAccessibilityModification(staticJsonValueGetter, methodType).let {
5555
MethodHandles.filterReturnValue(converter.unboxHandle, it)
5656
}
5757

src/main/kotlin/io/github/projectmapk/jackson/module/kogera/ser/serializers/KotlinSerializers.kt

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ import io.github.projectmapk.jackson.module.kogera.STRING_TO_ANY_METHOD_TYPE
2222
import io.github.projectmapk.jackson.module.kogera.StringValueClassUnboxConverter
2323
import io.github.projectmapk.jackson.module.kogera.ValueClassUnboxConverter
2424
import io.github.projectmapk.jackson.module.kogera.isUnboxableValueClass
25-
import io.github.projectmapk.jackson.module.kogera.unreflectAsType
25+
import io.github.projectmapk.jackson.module.kogera.unreflectAsTypeWithAccessibilityModification
2626
import java.lang.invoke.MethodHandle
2727
import java.lang.invoke.MethodHandles
2828
import java.lang.reflect.Method
@@ -83,39 +83,39 @@ internal sealed class ValueClassStaticJsonValueSerializer<T : Any>(
8383
staticJsonValueGetter: Method,
8484
) : ValueClassStaticJsonValueSerializer<T>(
8585
converter,
86-
unreflectAsType(staticJsonValueGetter, INT_TO_ANY_METHOD_TYPE),
86+
unreflectAsTypeWithAccessibilityModification(staticJsonValueGetter, INT_TO_ANY_METHOD_TYPE),
8787
)
8888

8989
internal class WrapsLong<T : Any>(
9090
converter: LongValueClassUnboxConverter<T>,
9191
staticJsonValueGetter: Method,
9292
) : ValueClassStaticJsonValueSerializer<T>(
9393
converter,
94-
unreflectAsType(staticJsonValueGetter, LONG_TO_ANY_METHOD_TYPE),
94+
unreflectAsTypeWithAccessibilityModification(staticJsonValueGetter, LONG_TO_ANY_METHOD_TYPE),
9595
)
9696

9797
internal class WrapsString<T : Any>(
9898
converter: StringValueClassUnboxConverter<T>,
9999
staticJsonValueGetter: Method,
100100
) : ValueClassStaticJsonValueSerializer<T>(
101101
converter,
102-
unreflectAsType(staticJsonValueGetter, STRING_TO_ANY_METHOD_TYPE),
102+
unreflectAsTypeWithAccessibilityModification(staticJsonValueGetter, STRING_TO_ANY_METHOD_TYPE),
103103
)
104104

105105
internal class WrapsJavaUuid<T : Any>(
106106
converter: JavaUuidValueClassUnboxConverter<T>,
107107
staticJsonValueGetter: Method,
108108
) : ValueClassStaticJsonValueSerializer<T>(
109109
converter,
110-
unreflectAsType(staticJsonValueGetter, JAVA_UUID_TO_ANY_METHOD_TYPE),
110+
unreflectAsTypeWithAccessibilityModification(staticJsonValueGetter, JAVA_UUID_TO_ANY_METHOD_TYPE),
111111
)
112112

113113
internal class WrapsAny<T : Any>(
114114
converter: GenericValueClassUnboxConverter<T>,
115115
staticJsonValueGetter: Method,
116116
) : ValueClassStaticJsonValueSerializer<T>(
117117
converter,
118-
unreflectAsType(staticJsonValueGetter, ANY_TO_ANY_METHOD_TYPE),
118+
unreflectAsTypeWithAccessibilityModification(staticJsonValueGetter, ANY_TO_ANY_METHOD_TYPE),
119119
)
120120

121121
companion object {
Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
package io.github.projectmapk.jackson.module.kogera.zIntegration.deser.valueClass
2+
3+
import io.github.projectmapk.jackson.module.kogera.defaultMapper
4+
import io.github.projectmapk.jackson.module.kogera.readValue
5+
import org.junit.jupiter.api.Assertions.assertEquals
6+
import org.junit.jupiter.api.Nested
7+
import org.junit.jupiter.api.Test
8+
9+
class PrivateConstructorTest {
10+
@JvmInline
11+
value class Primitive private constructor(val v: Int)
12+
13+
@JvmInline
14+
value class NonNullObject private constructor(val v: String)
15+
16+
@JvmInline
17+
value class NullableObject private constructor(val v: String?)
18+
19+
@JvmInline
20+
value class NullablePrimitive private constructor(val v: Int?)
21+
22+
@JvmInline
23+
value class TwoUnitPrimitive private constructor(val v: Long)
24+
25+
@Nested
26+
inner class DirectDeserializeTest {
27+
@Test
28+
fun primitiveTest() {
29+
val result = defaultMapper.readValue<Primitive>("1")
30+
assertEquals(1, result.v)
31+
}
32+
33+
@Test
34+
fun nonNullObjectTest() {
35+
val result = defaultMapper.readValue<NonNullObject>(""""foo"""")
36+
assertEquals("foo", result.v)
37+
}
38+
39+
@Test
40+
fun nullableObjectTest() {
41+
val result = defaultMapper.readValue<NullableObject>(""""bar"""")
42+
assertEquals("bar", result.v)
43+
}
44+
45+
@Test
46+
fun nullablePrimitiveTest() {
47+
val result = defaultMapper.readValue<NullablePrimitive>("2")
48+
assertEquals(2, result.v)
49+
}
50+
51+
@Test
52+
fun twoUnitPrimitiveTest() {
53+
val result = defaultMapper.readValue<TwoUnitPrimitive>("3")
54+
assertEquals(3L, result.v)
55+
}
56+
}
57+
58+
data class Dto(
59+
val primitive: Primitive,
60+
val nonNullObject: NonNullObject,
61+
val nullableObject: NullableObject,
62+
val nullablePrimitive: NullablePrimitive,
63+
val twoUnitPrimitive: TwoUnitPrimitive,
64+
)
65+
66+
@Test
67+
fun wrappedDeserializeTest() {
68+
val src = """{"primitive":1,"nonNullObject":"foo","nullableObject":"bar","nullablePrimitive":2,"twoUnitPrimitive":3}"""
69+
val result = defaultMapper.readValue<Dto>(src)
70+
assertEquals(1, result.primitive.v)
71+
assertEquals("foo", result.nonNullObject.v)
72+
assertEquals("bar", result.nullableObject.v)
73+
assertEquals(2, result.nullablePrimitive.v)
74+
assertEquals(3L, result.twoUnitPrimitive.v)
75+
}
76+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
package io.github.projectmapk.jackson.module.kogera.zIntegration.deser.valueClass.mapKey
2+
3+
import io.github.projectmapk.jackson.module.kogera.defaultMapper
4+
import io.github.projectmapk.jackson.module.kogera.readValue
5+
import org.junit.jupiter.api.Assertions.assertEquals
6+
import org.junit.jupiter.api.Nested
7+
import org.junit.jupiter.api.Test
8+
9+
class PrivateConstructorTest {
10+
@JvmInline
11+
value class Primitive private constructor(val v: Int)
12+
13+
@JvmInline
14+
value class NonNullObject private constructor(val v: String)
15+
16+
@JvmInline
17+
value class NullableObject private constructor(val v: String?)
18+
19+
@JvmInline
20+
value class NullablePrimitive private constructor(val v: Int?)
21+
22+
@JvmInline
23+
value class TwoUnitPrimitive private constructor(val v: Long)
24+
25+
@Nested
26+
inner class DirectDeserialize {
27+
@Test
28+
fun primitive() {
29+
val result = defaultMapper.readValue<Map<Primitive, String?>>("""{"1":null}""")
30+
assertEquals(1, result.keys.first().v)
31+
}
32+
33+
@Test
34+
fun nonNullObject() {
35+
val result = defaultMapper.readValue<Map<NonNullObject, String?>>("""{"foo":null}""")
36+
assertEquals("foo", result.keys.first().v)
37+
}
38+
39+
@Test
40+
fun nullableObject() {
41+
val result = defaultMapper.readValue<Map<NullableObject, String?>>("""{"bar":null}""")
42+
assertEquals("bar", result.keys.first().v)
43+
}
44+
45+
@Test
46+
fun nullablePrimitive() {
47+
val result = defaultMapper.readValue<Map<NullablePrimitive, String?>>("""{"2":null}""")
48+
assertEquals(2, result.keys.first().v)
49+
}
50+
51+
@Test
52+
fun twoUnitPrimitive() {
53+
val result = defaultMapper.readValue<Map<TwoUnitPrimitive, String?>>("""{"1":null}""")
54+
assertEquals(1L, result.keys.first().v)
55+
}
56+
}
57+
58+
data class Dst(
59+
val p: Map<Primitive, String?>,
60+
val nn: Map<NonNullObject, String?>,
61+
val n: Map<NullableObject, String?>,
62+
val np: Map<NullablePrimitive, String?>,
63+
val tup: Map<TwoUnitPrimitive, String?>,
64+
)
65+
66+
@Test
67+
fun wrapped() {
68+
val src = """
69+
{
70+
"p":{"1":null},
71+
"nn":{"foo":null},
72+
"n":{"bar":null},
73+
"np":{"2":null},
74+
"tup":{"2":null}
75+
}
76+
""".trimIndent()
77+
val result = defaultMapper.readValue<Dst>(src)
78+
assertEquals(1, result.p.keys.first().v)
79+
assertEquals("foo", result.nn.keys.first().v)
80+
assertEquals("bar", result.n.keys.first().v)
81+
assertEquals(2, result.np.keys.first().v)
82+
assertEquals(2L, result.tup.keys.first().v)
83+
}
84+
}

0 commit comments

Comments
 (0)