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
2 changes: 1 addition & 1 deletion .github/workflows/test-main.yml
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ jobs:
# LTS versions, latest version (if exists)
java-version: [ '17', '21', '24' ]
# Minimum version, latest release version, latest pre-release version (if exists)
kotlin: ['2.0.21', '2.1.21', '2.2.10']
kotlin: ['2.0.21', '2.1.21', '2.2.20']
env:
KOTLIN_VERSION: ${{ matrix.kotlin }}
name: "Kotlin ${{ matrix.kotlin }} - Java ${{ matrix.java-version }}"
Expand Down
2 changes: 1 addition & 1 deletion build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ val jacksonVersion = libs.versions.jackson.get()
val generatedSrcPath = "${layout.buildDirectory.get()}/generated/kotlin"

group = groupStr
version = "${jacksonVersion}-beta29"
version = "${jacksonVersion}-beta30"

repositories {
mavenCentral()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -127,7 +127,7 @@ internal class IntValueClassUnboxConverter<T : Any>(
unboxMethod: Method,
) : ValueClassUnboxConverter<T, Int>() {
override val unboxedType: Type get() = INT_CLASS
override val unboxHandle: MethodHandle = unreflectAsType(unboxMethod, ANY_TO_INT_METHOD_TYPE)
override val unboxHandle: MethodHandle = unreflectAsTypeWithAccessibilityModification(unboxMethod, ANY_TO_INT_METHOD_TYPE)

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

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

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

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

override fun convert(value: T): Any? = unboxHandle.invokeExact(value)
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import com.fasterxml.jackson.annotation.JsonCreator
import com.fasterxml.jackson.annotation.JsonProperty
import com.fasterxml.jackson.core.JsonParser
import com.fasterxml.jackson.databind.PropertyName
import com.fasterxml.jackson.databind.util.ClassUtil
import io.github.projectmapk.jackson.module.kogera.annotation.JsonKUnbox
import java.lang.invoke.MethodHandle
import java.lang.invoke.MethodHandles
Expand Down Expand Up @@ -83,8 +84,13 @@ internal val LONG_TO_ANY_METHOD_TYPE by lazy { MethodType.methodType(ANY_CLASS,
internal val STRING_TO_ANY_METHOD_TYPE by lazy { MethodType.methodType(ANY_CLASS, STRING_CLASS) }
internal val JAVA_UUID_TO_ANY_METHOD_TYPE by lazy { MethodType.methodType(ANY_CLASS, JAVA_UUID_CLASS) }

internal fun unreflect(method: Method): MethodHandle = MethodHandles.lookup().unreflect(method)
internal fun unreflectAsType(method: Method, type: MethodType): MethodHandle = unreflect(method).asType(type)
internal fun unreflectWithAccessibilityModification(method: Method): MethodHandle = MethodHandles.lookup().unreflect(
method.apply { ClassUtil.checkAndFixAccess(this, false) },
)
internal fun unreflectAsTypeWithAccessibilityModification(
method: Method,
type: MethodType,
): MethodHandle = unreflectWithAccessibilityModification(method).asType(type)

private val primitiveClassToDesc = mapOf(
Byte::class.java to 'B',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,8 +29,8 @@ import io.github.projectmapk.jackson.module.kogera.hasCreatorAnnotation
import io.github.projectmapk.jackson.module.kogera.isUnboxableValueClass
import io.github.projectmapk.jackson.module.kogera.jmClass.JmClass
import io.github.projectmapk.jackson.module.kogera.toSignature
import io.github.projectmapk.jackson.module.kogera.unreflect
import io.github.projectmapk.jackson.module.kogera.unreflectAsType
import io.github.projectmapk.jackson.module.kogera.unreflectAsTypeWithAccessibilityModification
import io.github.projectmapk.jackson.module.kogera.unreflectWithAccessibilityModification
import java.lang.invoke.MethodHandle
import java.lang.invoke.MethodHandles
import java.lang.reflect.Method
Expand Down Expand Up @@ -110,7 +110,7 @@ internal sealed class NoConversionCreatorBoxDeserializer<S, D : Any>(
) : WrapsNullableValueClassDeserializer<D>(converter.boxedClass) {
protected abstract val inputType: Class<*>
protected val handle: MethodHandle = MethodHandles
.filterReturnValue(unreflect(creator), converter.boxHandle)
.filterReturnValue(unreflectWithAccessibilityModification(creator), converter.boxHandle)

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

init {
val unreflect = unreflect(creator).run {
val unreflect = unreflectWithAccessibilityModification(creator).run {
asType(type().changeParameterType(0, ANY_CLASS))
}
handle = MethodHandles.filterReturnValue(unreflect, converter.boxHandle)
Expand Down Expand Up @@ -223,7 +223,7 @@ internal class WrapsAnyValueClassBoxDeserializer<S, D : Any>(
private val handle: MethodHandle

init {
val unreflect = unreflectAsType(creator, ANY_TO_ANY_METHOD_TYPE)
val unreflect = unreflectAsTypeWithAccessibilityModification(creator, ANY_TO_ANY_METHOD_TYPE)
handle = MethodHandles.filterReturnValue(unreflect, converter.boxHandle)
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,8 @@ import io.github.projectmapk.jackson.module.kogera.StringValueClassBoxConverter
import io.github.projectmapk.jackson.module.kogera.ValueClassBoxConverter
import io.github.projectmapk.jackson.module.kogera.isUnboxableValueClass
import io.github.projectmapk.jackson.module.kogera.toSignature
import io.github.projectmapk.jackson.module.kogera.unreflect
import io.github.projectmapk.jackson.module.kogera.unreflectAsType
import io.github.projectmapk.jackson.module.kogera.unreflectAsTypeWithAccessibilityModification
import io.github.projectmapk.jackson.module.kogera.unreflectWithAccessibilityModification
import java.lang.invoke.MethodHandle
import java.lang.invoke.MethodHandles
import java.lang.reflect.Method
Expand Down Expand Up @@ -101,7 +101,7 @@ internal sealed class ValueClassKeyDeserializer<S, D : Any>(
// Currently, only the primary constructor can be the creator of a key, so for specified types,
// the return type of the primary constructor and the input type of the box function are exactly the same.
// Therefore, performance is improved by omitting the asType call.
unreflect(creator),
unreflectWithAccessibilityModification(creator),
)

internal class WrapsInt<D : Any>(
Expand Down Expand Up @@ -149,7 +149,7 @@ internal sealed class ValueClassKeyDeserializer<S, D : Any>(
creator: Method,
) : ValueClassKeyDeserializer<S, D>(
converter,
unreflectAsType(creator, ANY_TO_ANY_METHOD_TYPE),
unreflectAsTypeWithAccessibilityModification(creator, ANY_TO_ANY_METHOD_TYPE),
) {
override val unboxedClass: Class<*> = creator.returnType

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ import io.github.projectmapk.jackson.module.kogera.STRING_TO_ANY_METHOD_TYPE
import io.github.projectmapk.jackson.module.kogera.StringValueClassUnboxConverter
import io.github.projectmapk.jackson.module.kogera.ValueClassUnboxConverter
import io.github.projectmapk.jackson.module.kogera.isUnboxableValueClass
import io.github.projectmapk.jackson.module.kogera.unreflectAsType
import io.github.projectmapk.jackson.module.kogera.unreflectAsTypeWithAccessibilityModification
import java.lang.invoke.MethodHandle
import java.lang.invoke.MethodHandles
import java.lang.invoke.MethodType
Expand Down Expand Up @@ -51,7 +51,7 @@ internal sealed class ValueClassStaticJsonKeySerializer<T : Any>(
methodType: MethodType,
) : StdSerializer<T>(converter.valueClass) {
private val keyType: Class<*> = staticJsonValueGetter.returnType
private val handle: MethodHandle = unreflectAsType(staticJsonValueGetter, methodType).let {
private val handle: MethodHandle = unreflectAsTypeWithAccessibilityModification(staticJsonValueGetter, methodType).let {
MethodHandles.filterReturnValue(converter.unboxHandle, it)
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ import io.github.projectmapk.jackson.module.kogera.STRING_TO_ANY_METHOD_TYPE
import io.github.projectmapk.jackson.module.kogera.StringValueClassUnboxConverter
import io.github.projectmapk.jackson.module.kogera.ValueClassUnboxConverter
import io.github.projectmapk.jackson.module.kogera.isUnboxableValueClass
import io.github.projectmapk.jackson.module.kogera.unreflectAsType
import io.github.projectmapk.jackson.module.kogera.unreflectAsTypeWithAccessibilityModification
import java.lang.invoke.MethodHandle
import java.lang.invoke.MethodHandles
import java.lang.reflect.Method
Expand Down Expand Up @@ -83,39 +83,39 @@ internal sealed class ValueClassStaticJsonValueSerializer<T : Any>(
staticJsonValueGetter: Method,
) : ValueClassStaticJsonValueSerializer<T>(
converter,
unreflectAsType(staticJsonValueGetter, INT_TO_ANY_METHOD_TYPE),
unreflectAsTypeWithAccessibilityModification(staticJsonValueGetter, INT_TO_ANY_METHOD_TYPE),
)

internal class WrapsLong<T : Any>(
converter: LongValueClassUnboxConverter<T>,
staticJsonValueGetter: Method,
) : ValueClassStaticJsonValueSerializer<T>(
converter,
unreflectAsType(staticJsonValueGetter, LONG_TO_ANY_METHOD_TYPE),
unreflectAsTypeWithAccessibilityModification(staticJsonValueGetter, LONG_TO_ANY_METHOD_TYPE),
)

internal class WrapsString<T : Any>(
converter: StringValueClassUnboxConverter<T>,
staticJsonValueGetter: Method,
) : ValueClassStaticJsonValueSerializer<T>(
converter,
unreflectAsType(staticJsonValueGetter, STRING_TO_ANY_METHOD_TYPE),
unreflectAsTypeWithAccessibilityModification(staticJsonValueGetter, STRING_TO_ANY_METHOD_TYPE),
)

internal class WrapsJavaUuid<T : Any>(
converter: JavaUuidValueClassUnboxConverter<T>,
staticJsonValueGetter: Method,
) : ValueClassStaticJsonValueSerializer<T>(
converter,
unreflectAsType(staticJsonValueGetter, JAVA_UUID_TO_ANY_METHOD_TYPE),
unreflectAsTypeWithAccessibilityModification(staticJsonValueGetter, JAVA_UUID_TO_ANY_METHOD_TYPE),
)

internal class WrapsAny<T : Any>(
converter: GenericValueClassUnboxConverter<T>,
staticJsonValueGetter: Method,
) : ValueClassStaticJsonValueSerializer<T>(
converter,
unreflectAsType(staticJsonValueGetter, ANY_TO_ANY_METHOD_TYPE),
unreflectAsTypeWithAccessibilityModification(staticJsonValueGetter, ANY_TO_ANY_METHOD_TYPE),
)

companion object {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
package io.github.projectmapk.jackson.module.kogera.zIntegration.deser.valueClass

import io.github.projectmapk.jackson.module.kogera.defaultMapper
import io.github.projectmapk.jackson.module.kogera.readValue
import org.junit.jupiter.api.Assertions.assertEquals
import org.junit.jupiter.api.Nested
import org.junit.jupiter.api.Test

class PrivateConstructorTest {
@JvmInline
value class Primitive private constructor(val v: Int)

@JvmInline
value class NonNullObject private constructor(val v: String)

@JvmInline
value class NullableObject private constructor(val v: String?)

@JvmInline
value class NullablePrimitive private constructor(val v: Int?)

@JvmInline
value class TwoUnitPrimitive private constructor(val v: Long)

@Nested
inner class DirectDeserializeTest {
@Test
fun primitiveTest() {
val result = defaultMapper.readValue<Primitive>("1")
assertEquals(1, result.v)
}

@Test
fun nonNullObjectTest() {
val result = defaultMapper.readValue<NonNullObject>(""""foo"""")
assertEquals("foo", result.v)
}

@Test
fun nullableObjectTest() {
val result = defaultMapper.readValue<NullableObject>(""""bar"""")
assertEquals("bar", result.v)
}

@Test
fun nullablePrimitiveTest() {
val result = defaultMapper.readValue<NullablePrimitive>("2")
assertEquals(2, result.v)
}

@Test
fun twoUnitPrimitiveTest() {
val result = defaultMapper.readValue<TwoUnitPrimitive>("3")
assertEquals(3L, result.v)
}
}

data class Dto(
val primitive: Primitive,
val nonNullObject: NonNullObject,
val nullableObject: NullableObject,
val nullablePrimitive: NullablePrimitive,
val twoUnitPrimitive: TwoUnitPrimitive,
)

@Test
fun wrappedDeserializeTest() {
val src = """{"primitive":1,"nonNullObject":"foo","nullableObject":"bar","nullablePrimitive":2,"twoUnitPrimitive":3}"""
val result = defaultMapper.readValue<Dto>(src)
assertEquals(1, result.primitive.v)
assertEquals("foo", result.nonNullObject.v)
assertEquals("bar", result.nullableObject.v)
assertEquals(2, result.nullablePrimitive.v)
assertEquals(3L, result.twoUnitPrimitive.v)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
package io.github.projectmapk.jackson.module.kogera.zIntegration.deser.valueClass.mapKey

import io.github.projectmapk.jackson.module.kogera.defaultMapper
import io.github.projectmapk.jackson.module.kogera.readValue
import org.junit.jupiter.api.Assertions.assertEquals
import org.junit.jupiter.api.Nested
import org.junit.jupiter.api.Test

class PrivateConstructorTest {
@JvmInline
value class Primitive private constructor(val v: Int)

@JvmInline
value class NonNullObject private constructor(val v: String)

@JvmInline
value class NullableObject private constructor(val v: String?)

@JvmInline
value class NullablePrimitive private constructor(val v: Int?)

@JvmInline
value class TwoUnitPrimitive private constructor(val v: Long)

@Nested
inner class DirectDeserialize {
@Test
fun primitive() {
val result = defaultMapper.readValue<Map<Primitive, String?>>("""{"1":null}""")
assertEquals(1, result.keys.first().v)
}

@Test
fun nonNullObject() {
val result = defaultMapper.readValue<Map<NonNullObject, String?>>("""{"foo":null}""")
assertEquals("foo", result.keys.first().v)
}

@Test
fun nullableObject() {
val result = defaultMapper.readValue<Map<NullableObject, String?>>("""{"bar":null}""")
assertEquals("bar", result.keys.first().v)
}

@Test
fun nullablePrimitive() {
val result = defaultMapper.readValue<Map<NullablePrimitive, String?>>("""{"2":null}""")
assertEquals(2, result.keys.first().v)
}

@Test
fun twoUnitPrimitive() {
val result = defaultMapper.readValue<Map<TwoUnitPrimitive, String?>>("""{"1":null}""")
assertEquals(1L, result.keys.first().v)
}
}

data class Dst(
val p: Map<Primitive, String?>,
val nn: Map<NonNullObject, String?>,
val n: Map<NullableObject, String?>,
val np: Map<NullablePrimitive, String?>,
val tup: Map<TwoUnitPrimitive, String?>,
)

@Test
fun wrapped() {
val src = """
{
"p":{"1":null},
"nn":{"foo":null},
"n":{"bar":null},
"np":{"2":null},
"tup":{"2":null}
}
""".trimIndent()
val result = defaultMapper.readValue<Dst>(src)
assertEquals(1, result.p.keys.first().v)
assertEquals("foo", result.nn.keys.first().v)
assertEquals("bar", result.n.keys.first().v)
assertEquals(2, result.np.keys.first().v)
assertEquals(2L, result.tup.keys.first().v)
}
}
Loading
Loading