Skip to content

Commit

Permalink
Merge pull request #212 from ProjectMapK/develop
Browse files Browse the repository at this point in the history
Release 2024-01-21 12:31:31 +0000
  • Loading branch information
k163377 committed Jan 21, 2024
2 parents 8d75abc + a2e9042 commit b9c4b1f
Show file tree
Hide file tree
Showing 36 changed files with 15,660 additions and 155 deletions.
3 changes: 2 additions & 1 deletion build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ val jacksonVersion = libs.versions.jackson.get()
val generatedSrcPath = "${layout.buildDirectory.get()}/generated/kotlin"

group = groupStr
version = "${jacksonVersion}-beta9"
version = "${jacksonVersion}-beta10"

repositories {
mavenCentral()
Expand All @@ -32,6 +32,7 @@ dependencies {
api(libs.jackson.annotations)

// test libs
testImplementation("${libs.kotlin.reflect.get()}:${kotlinVersion}")
testImplementation(libs.junit.api)
testImplementation(libs.junit.params)
testRuntimeOnly(libs.junit.engine)
Expand Down
3 changes: 2 additions & 1 deletion gradle/libs.versions.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[versions]
kotlin = "1.8.22" # Mainly for CI, it can be rewritten by environment variable.
jackson = "2.16.0"
jackson = "2.16.1"

# test libs
junit = "5.10.1"
Expand All @@ -12,6 +12,7 @@ jackson-databind = { module = "com.fasterxml.jackson.core:jackson-databind", ver
jackson-annotations = { module = "com.fasterxml.jackson.core:jackson-annotations", version.ref = "jackson" }

# test libs
kotlin-reflect = { module = "org.jetbrains.kotlin:kotlin-reflect" }
junit-api = { module = "org.junit.jupiter:junit-jupiter-api", version.ref = "junit" }
junit-params = { module = "org.junit.jupiter:junit-jupiter-params", version.ref = "junit" }
junit-engine = { module = "org.junit.jupiter:junit-jupiter-engine", version.ref = "junit" }
Expand Down
Original file line number Diff line number Diff line change
@@ -1,32 +1,37 @@
package io.github.projectmapk.jackson.module.kogera.deser;

import com.fasterxml.jackson.core.JacksonException;
import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.databind.DeserializationContext;
import com.fasterxml.jackson.databind.JavaType;
import com.fasterxml.jackson.databind.deser.std.StdDeserializer;
import io.github.projectmapk.jackson.module.kogera.deser.deserializers.ValueClassBoxDeserializer;
import io.github.projectmapk.jackson.module.kogera.deser.deserializers.WrapsNullableValueClassBoxDeserializer;
import kotlin.jvm.JvmClassMappingKt;
import kotlin.reflect.KClass;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;

import java.io.IOException;

/**
* An interface to be inherited by JsonDeserializer that handles value classes that may wrap nullable.
* @see ValueClassBoxDeserializer for implementation.
* @see WrapsNullableValueClassBoxDeserializer for implementation.
*/
// To ensure maximum compatibility with StdDeserializer, this class is defined in Java.
public abstract class ValueClassDeserializer<D> extends StdDeserializer<D> {
protected ValueClassDeserializer(@NotNull KClass<?> vc) {
// To ensure maximum compatibility with StdDeserializer, this class is written in Java.
public abstract class WrapsNullableValueClassDeserializer<D> extends StdDeserializer<D> {
protected WrapsNullableValueClassDeserializer(@NotNull KClass<?> vc) {
super(JvmClassMappingKt.getJavaClass(vc));
}

protected ValueClassDeserializer(@NotNull Class<?> vc) {
protected WrapsNullableValueClassDeserializer(@NotNull Class<?> vc) {
super(vc);
}

protected ValueClassDeserializer(@NotNull JavaType valueType) {
protected WrapsNullableValueClassDeserializer(@NotNull JavaType valueType) {
super(valueType);
}

protected ValueClassDeserializer(@NotNull StdDeserializer<D> src) {
protected WrapsNullableValueClassDeserializer(@NotNull StdDeserializer<D> src) {
super(src);
}

Expand All @@ -44,4 +49,8 @@ public final Class<D> handledType() {
// It is defined so that null can also be returned so that Nulls.SKIP can be applied.
@Nullable
public abstract D getBoxedNullValue();

@Override
public abstract D deserialize(@NotNull JsonParser p, @NotNull DeserializationContext ctxt)
throws IOException, JacksonException;
}
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,6 @@ internal class ValueClassUnboxConverter<T : Any>(val valueClass: Class<T>) : Std
private val unboxMethod = valueClass.getDeclaredMethod("unbox-impl").apply {
if (!this.isAccessible) this.isAccessible = true
}
val unboxedClass: Class<*> = unboxMethod.returnType

override fun convert(value: T): Any? = unboxMethod.invoke(value)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ import java.lang.reflect.Method
internal typealias JavaDuration = java.time.Duration
internal typealias KotlinDuration = kotlin.time.Duration

internal fun Class<*>.isUnboxableValueClass() = this.getAnnotation(JvmInline::class.java) != null
internal fun Class<*>.isUnboxableValueClass() = this.isAnnotationPresent(JvmInline::class.java)

// JmClass must be value class.
internal fun JmClass.wrapsNullValueClass() = inlineClassUnderlyingType!!.isNullable
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ internal object KotlinClassIntrospector : BasicClassIntrospector() {
?: run {
val coll = collectProperties(config, type, r, true)

if (type.rawClass.annotations.any { it is Metadata }) {
if (type.rawClass.isAnnotationPresent(Metadata::class.java)) {
KotlinBeanDescription(coll)
} else {
BasicBeanDescription.forDeserialization(coll)
Expand All @@ -71,7 +71,7 @@ internal object KotlinClassIntrospector : BasicClassIntrospector() {
?: run {
val coll = collectProperties(config, type, r, false)

if (type.rawClass.annotations.any { it is Metadata }) {
if (type.rawClass.isAnnotationPresent(Metadata::class.java)) {
KotlinBeanDescription(coll)
} else {
BasicBeanDescription.forDeserialization(coll)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,7 @@ internal class KotlinFallbackAnnotationIntrospector(
}
} else {
// If JsonUnbox is specified, the unboxed getter is used as is.
if (a.hasAnnotation(JsonKUnbox::class.java) || it.getAnnotation(JsonKUnbox::class.java) != null) {
if (a.hasAnnotation(JsonKUnbox::class.java) || it.isAnnotationPresent(JsonKUnbox::class.java)) {
null
} else {
cache.getValueClassBoxConverter(a.rawReturnType, it)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ import io.github.projectmapk.jackson.module.kogera.KotlinDuration
import io.github.projectmapk.jackson.module.kogera.ReflectionCache
import io.github.projectmapk.jackson.module.kogera.ValueClassBoxConverter
import io.github.projectmapk.jackson.module.kogera.deser.JavaToKotlinDurationConverter
import io.github.projectmapk.jackson.module.kogera.deser.ValueClassDeserializer
import io.github.projectmapk.jackson.module.kogera.deser.WrapsNullableValueClassDeserializer
import io.github.projectmapk.jackson.module.kogera.hasCreatorAnnotation
import io.github.projectmapk.jackson.module.kogera.isUnboxableValueClass
import io.github.projectmapk.jackson.module.kogera.toSignature
Expand Down Expand Up @@ -89,10 +89,10 @@ internal object ULongDeserializer : StdDeserializer<ULong>(ULong::class.java) {
ULongChecker.readWithRangeCheck(p, p.bigIntegerValue)
}

internal class ValueClassBoxDeserializer<S, D : Any>(
internal class WrapsNullableValueClassBoxDeserializer<S, D : Any>(
private val creator: Method,
private val converter: ValueClassBoxConverter<S, D>
) : ValueClassDeserializer<D>(converter.boxedClass) {
) : WrapsNullableValueClassDeserializer<D>(converter.boxedClass) {
private val inputType: Class<*> = creator.parameterTypes[0]

init {
Expand Down Expand Up @@ -169,9 +169,9 @@ internal class KotlinDeserializers(
rawClass == KotlinDuration::class.java ->
JavaToKotlinDurationConverter.takeIf { useJavaDurationConversion }?.delegatingDeserializer
rawClass.isUnboxableValueClass() -> findValueCreator(type, rawClass, cache.getJmClass(rawClass)!!)?.let {
val unboxedClass = cache.getValueClassUnboxConverter(rawClass).unboxedClass
val unboxedClass = it.returnType
val converter = cache.getValueClassBoxConverter(unboxedClass, rawClass)
ValueClassBoxDeserializer(it, converter)
WrapsNullableValueClassBoxDeserializer(it, converter)
}
else -> null
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ import com.fasterxml.jackson.databind.deser.std.StdValueInstantiator
import com.fasterxml.jackson.databind.exc.InvalidNullException
import com.fasterxml.jackson.databind.module.SimpleValueInstantiators
import io.github.projectmapk.jackson.module.kogera.ReflectionCache
import io.github.projectmapk.jackson.module.kogera.deser.ValueClassDeserializer
import io.github.projectmapk.jackson.module.kogera.deser.WrapsNullableValueClassDeserializer
import io.github.projectmapk.jackson.module.kogera.deser.valueInstantiator.creator.ConstructorValueCreator
import io.github.projectmapk.jackson.module.kogera.deser.valueInstantiator.creator.MethodValueCreator
import io.github.projectmapk.jackson.module.kogera.deser.valueInstantiator.creator.ValueCreator
Expand Down Expand Up @@ -47,7 +47,7 @@ internal class KotlinValueInstantiator(
isNullableParam: Boolean,
valueDeserializer: JsonDeserializer<*>?
): Boolean = !isNullableParam &&
valueDeserializer is ValueClassDeserializer<*> &&
valueDeserializer is WrapsNullableValueClassDeserializer<*> &&
cache.getJmClass(valueDeserializer.handledType())!!.wrapsNullValueClass()

private val valueCreator: ValueCreator<*>? by ReflectProperties.lazySoft {
Expand Down Expand Up @@ -83,7 +83,7 @@ internal class KotlinValueInstantiator(
// Deserializer.getNullValue could not be used because there is no way to get and parse parameters
// from the BeanDescription and using AnnotationIntrospector would override user customization.
if (requireValueClassSpecialNullValue(paramDef.isNullable, valueDeserializer)) {
(valueDeserializer as ValueClassDeserializer<*>).boxedNullValue?.let { return@run it }
(valueDeserializer as WrapsNullableValueClassDeserializer<*>).boxedNullValue?.let { return@run it }
}

if (jsonProp.skipNulls() && paramDef.isOptional) return@forEachIndexed
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,10 @@ import com.fasterxml.jackson.core.util.DefaultIndenter
import com.fasterxml.jackson.core.util.DefaultPrettyPrinter
import com.fasterxml.jackson.databind.ObjectMapper
import com.fasterxml.jackson.databind.ObjectWriter
import org.junit.jupiter.api.Assertions.assertEquals
import kotlin.reflect.KParameter
import kotlin.reflect.full.memberProperties
import kotlin.reflect.full.primaryConstructor

// This `printer` is used to match the output from Jackson to the newline char of the source code.
// If this is removed, comparisons will fail in a Windows-like platform.
Expand All @@ -13,3 +17,18 @@ internal val LF_PRINTER: DefaultPrettyPrinter =
internal fun ObjectMapper.testPrettyWriter(): ObjectWriter = this.writer(LF_PRINTER)

internal fun Class<*>.isKotlinClass() = declaredAnnotations.any { it is Metadata }

internal val defaultMapper = jacksonObjectMapper()

internal inline fun <reified T : Any> callPrimaryConstructor(mapper: (KParameter) -> Any? = { it.name }): T =
T::class.primaryConstructor!!.run {
val args = parameters.associateWith { mapper(it) }
callBy(args)
}

// Function for comparing non-data classes.
internal inline fun <reified T : Any> assertReflectEquals(expected: T, actual: T) {
T::class.memberProperties.forEach {
assertEquals(it.get(expected), it.get(actual))
}
}
Original file line number Diff line number Diff line change
@@ -1,12 +1,14 @@
package io.github.projectmapk.jackson.module.kogera.zIntegration

import com.fasterxml.jackson.databind.ObjectMapper
import org.junit.jupiter.api.Assertions.assertTrue
import org.junit.jupiter.api.Test
import org.junit.jupiter.api.assertDoesNotThrow

class InitModuleTest {
@Test
fun findAndRegisterModulesTest() {
assertDoesNotThrow { ObjectMapper().findAndRegisterModules() }
val mapper = assertDoesNotThrow { ObjectMapper().findAndRegisterModules() }
assertTrue(mapper.registeredModuleIds.contains("io.github.projectmapk.jackson.module.kogera.KotlinModule"))
}
}

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -28,12 +28,12 @@ class HasRequiredMarkerTest {
)

class ConstructorParamTarget(
vararg val vararg: Int, // Kotlin allows vararg to be set other than at the tail.
val nullable: String?,
val hasDefault: String = "default",
val collection: Collection<*>,
val map: Map<*, *>,
val nonNull: Any,
vararg val vararg: Int
val nonNull: Any
)

@Nested
Expand All @@ -42,24 +42,24 @@ class HasRequiredMarkerTest {
fun defaultParam() {
val desc = defaultMapper.introspectDeser<ConstructorParamTarget>()

assertFalse(desc.isRequired("vararg"))
assertFalse(desc.isRequired("nullable"))
assertFalse(desc.isRequired("hasDefault"))
assertTrue(desc.isRequired("collection"))
assertTrue(desc.isRequired("map"))
assertTrue(desc.isRequired("nonNull"))
assertFalse(desc.isRequired("vararg"))
}

@Test
fun nullToDefaultParam() {
val desc = nullToDefaultMapper.introspectDeser<ConstructorParamTarget>()

assertFalse(desc.isRequired("vararg"))
assertFalse(desc.isRequired("nullable"))
assertFalse(desc.isRequired("hasDefault"))
assertFalse(desc.isRequired("collection"))
assertFalse(desc.isRequired("map"))
assertTrue(desc.isRequired("nonNull"))
assertFalse(desc.isRequired("vararg"))
}
}

Expand Down
Loading

0 comments on commit b9c4b1f

Please sign in to comment.