Skip to content

Commit

Permalink
Merge pull request #227 from ProjectMapK/develop
Browse files Browse the repository at this point in the history
Release 2024-03-30 06:53:34 +0000
  • Loading branch information
k163377 authored Mar 30, 2024
2 parents c682e69 + 06d5964 commit a10665c
Show file tree
Hide file tree
Showing 15 changed files with 51 additions and 47 deletions.
4 changes: 0 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,10 +23,6 @@ First, by implementing the equivalent of https://github.com/FasterXML/jackson-mo
The cache has also been reorganized based on [benchmark results](https://github.com/ProjectMapK/kogera-benchmark) to achieve smaller memory consumption.
The performance degradation when the `strictNullChecks` option is enabled is also [greatly reduced](https://github.com/ProjectMapK/jackson-module-kogera/pull/44).

The next main feature is `value class` support.
The `jackson-module-kogera` supports many use cases for `value class`, including deserialization.
See [here](./docs/AboutValueClassSupport.md) for basic policies and notes on handling `value class`.

[Here](./docs/FixedIssues.md) is a list of issues that are not resolved in `jackson-module-kotlin` but are or will be resolved in `kogera`.

## About intentional destructive changes
Expand Down
2 changes: 1 addition & 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}-beta11"
version = "${jacksonVersion}-beta12"

repositories {
mavenCentral()
Expand Down
3 changes: 0 additions & 3 deletions docs/FixedIssues.md
Original file line number Diff line number Diff line change
@@ -1,15 +1,12 @@
A list of issues that have not been resolved in `jackson-module-kotlin`, but have been or will be resolved in `kogera`.

## Fixed
- [Support for inline classes · Issue \#199](https://github.com/FasterXML/jackson-module-kotlin/issues/199)
- [Getting MismatchedInputException instead of MissingKotlinParameterException · Issue \#234](https://github.com/FasterXML/jackson-module-kotlin/issues/234)
- [Private getter with different return type hides property · Issue \#341](https://github.com/FasterXML/jackson-module-kotlin/issues/341)
- [Remove \`kotlin\-reflect\` and replace it with \`kotlinx\-metadata\-jvm\` · Issue \#450](https://github.com/FasterXML/jackson-module-kotlin/issues/450)
- [Supports deserialization of \`value class\` \(\`inline class\`\)\. · Issue \#650](https://github.com/FasterXML/jackson-module-kotlin/issues/650)
- [Annotation given to constructor parameters containing \`value class\` as argument does not work · Issue \#651](https://github.com/FasterXML/jackson-module-kotlin/issues/651)
- [How to deserialize a kotlin\.ranges\.ClosedRange<T> with Jackson · Issue \#663](https://github.com/FasterXML/jackson-module-kotlin/issues/663)
- [There are cases where \`isRequired\` specifications are not properly merged\. · Issue \#668](https://github.com/FasterXML/jackson-module-kotlin/issues/668)

## Want to fix
- [There are some problems with KNAI\.hasCreatorAnnotation · Issue \#547](https://github.com/FasterXML/jackson-module-kotlin/issues/547)
- [This module shouldn't bring kotlin\-reflect 1\.5 as a transitive dependency · Issue \#566](https://github.com/FasterXML/jackson-module-kotlin/issues/566)
4 changes: 0 additions & 4 deletions docs/KogeraSpecificImplementations.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,3 @@ I will consider making the classes public again when we receive requests for the
## Remove old options and `deprecated` code
Because `jackson-module-kotlin` is a framework with a long history, some old code and options remain.
In `kogera`, those options have been removed.

# Value class support
The `jackson-module-kogera` supports many use cases of `value class` (`inline class`).
This is summarized [here](./AboutValueClassSupport.md).
6 changes: 3 additions & 3 deletions gradle/libs.versions.toml
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
[versions]
kotlin = "1.8.22" # Mainly for CI, it can be rewritten by environment variable.
jackson = "2.16.1"
jackson = "2.17.0"

# test libs
junit = "5.10.1"
junit = "5.10.2"

[libraries]
kotlin-stdlib = { module = "org.jetbrains.kotlin:kotlin-stdlib" }
Expand All @@ -16,7 +16,7 @@ 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" }
mockk = "io.mockk:mockk:1.13.7"
mockk = "io.mockk:mockk:1.13.10"
jackson-xml = { module = "com.fasterxml.jackson.dataformat:jackson-dataformat-xml", version.ref = "jackson" }
jackson-jsr310 = { module = "com.fasterxml.jackson.datatype:jackson-datatype-jsr310", version.ref = "jackson" }

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -55,8 +55,8 @@ internal object ClosedRangeResolver : SimpleAbstractTypeResolver() {
}

fun findClosedFloatingPointRangeRef(contentType: Class<*>): Class<*>? = when (contentType) {
Double::class.javaPrimitiveType, Double::class.javaObjectType -> closedDoubleRangeRef
Float::class.javaPrimitiveType, Float::class.javaObjectType -> closedFloatRangeRef
Double::class.java, Double::class.javaObjectType -> closedDoubleRangeRef
Float::class.java, Float::class.javaObjectType -> closedFloatRangeRef
else -> null
}

Expand All @@ -67,7 +67,7 @@ internal object ClosedRangeResolver : SimpleAbstractTypeResolver() {
override fun findTypeMapping(config: DeserializationConfig, type: JavaType): JavaType? {
val rawClass = type.rawClass

return if (rawClass == ClosedRange::class.java || rawClass == ClosedFloatingPointRange::class.java) {
return if (rawClass == ClosedRange::class.java || rawClass == CLOSED_FLOATING_POINT_RANGE_CLASS) {
type.bindings.typeParameters.firstOrNull()
?.let { typeParam ->
findClosedFloatingPointRangeRef(typeParam.rawClass)?.let {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
package io.github.projectmapk.jackson.module.kogera

import com.fasterxml.jackson.annotation.JsonCreator
import com.fasterxml.jackson.annotation.JsonProperty
import io.github.projectmapk.jackson.module.kogera.annotation.JsonKUnbox
import kotlinx.metadata.KmClass
import kotlinx.metadata.KmClassifier
import kotlinx.metadata.KmType
Expand All @@ -14,25 +16,25 @@ import java.lang.reflect.Method
internal typealias JavaDuration = java.time.Duration
internal typealias KotlinDuration = kotlin.time.Duration

internal fun Class<*>.toKmClass(): KmClass? = getAnnotation(Metadata::class.java)?.let {
internal fun Class<*>.toKmClass(): KmClass? = getAnnotation(METADATA_CLASS)?.let {
(KotlinClassMetadata.readStrict(it) as KotlinClassMetadata.Class).kmClass
}

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

// JmClass must be value class.
internal fun JmClass.wrapsNullValueClass() = inlineClassUnderlyingType!!.isNullable

private val primitiveClassToDesc = mapOf(
Byte::class.javaPrimitiveType to 'B',
Char::class.javaPrimitiveType to 'C',
Double::class.javaPrimitiveType to 'D',
Float::class.javaPrimitiveType to 'F',
Int::class.javaPrimitiveType to 'I',
Long::class.javaPrimitiveType to 'J',
Short::class.javaPrimitiveType to 'S',
Boolean::class.javaPrimitiveType to 'Z',
Void::class.javaPrimitiveType to 'V'
Byte::class.java to 'B',
Char::class.java to 'C',
Double::class.java to 'D',
Float::class.java to 'F',
Int::class.java to 'I',
Long::class.java to 'J',
Short::class.java to 'S',
Boolean::class.java to 'Z',
Void.TYPE to 'V'
)

// -> this.name.replace(".", "/")
Expand Down Expand Up @@ -88,6 +90,19 @@ internal fun String.reconstructClass(): Class<*> {
internal fun KmType.reconstructClassOrNull(): Class<*>? = (classifier as? KmClassifier.Class)
?.let { kotlin.runCatching { it.name.reconstructClass() }.getOrNull() }

internal fun AnnotatedElement.hasCreatorAnnotation(): Boolean = getAnnotation(JsonCreator::class.java)
internal fun AnnotatedElement.hasCreatorAnnotation(): Boolean = getAnnotation(JSON_CREATOR_CLASS)
?.let { it.mode != JsonCreator.Mode.DISABLED }
?: false

// Instantiating Java Class as a static property is expected to improve first-time execution performance.
// However, maybe this improvement is limited to Java Classes that are not used to initialize static content.
// Also, for classes that are read at the time of initialization of static content or module initialization,
// optimization seems unnecessary because caching is effective.
internal val METADATA_CLASS = Metadata::class.java
internal val JVM_INLINE_CLASS = JvmInline::class.java
internal val JSON_CREATOR_CLASS = JsonCreator::class.java
internal val JSON_PROPERTY_CLASS = JsonProperty::class.java
internal val JSON_K_UNBOX_CLASS = JsonKUnbox::class.java
internal val KOTLIN_DURATION_CLASS = KotlinDuration::class.java
internal val CLOSED_FLOATING_POINT_RANGE_CLASS = ClosedFloatingPointRange::class.java
internal val ANY_CLASS = Any::class.java
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.isAnnotationPresent(Metadata::class.java)) {
if (type.rawClass.isAnnotationPresent(METADATA_CLASS)) {
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.isAnnotationPresent(Metadata::class.java)) {
if (type.rawClass.isAnnotationPresent(METADATA_CLASS)) {
KotlinBeanDescription(coll)
} else {
BasicBeanDescription.forDeserialization(coll)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ internal class ReflectionCache(initialCacheSize: Int, maxCacheSize: Int) : Seria
val superJmClass = if (!clazz.isInterface) {
clazz.superclass?.let {
// Stop parsing when `Object` is reached
if (it != Any::class.java) getJmClass(it) else null
if (it != ANY_CLASS) getJmClass(it) else null
}
} else {
null
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,9 @@ import com.fasterxml.jackson.databind.introspect.AnnotatedMethod
import com.fasterxml.jackson.databind.introspect.AnnotatedParameter
import com.fasterxml.jackson.databind.introspect.NopAnnotationIntrospector
import com.fasterxml.jackson.databind.util.Converter
import io.github.projectmapk.jackson.module.kogera.KotlinDuration
import io.github.projectmapk.jackson.module.kogera.JSON_K_UNBOX_CLASS
import io.github.projectmapk.jackson.module.kogera.KOTLIN_DURATION_CLASS
import io.github.projectmapk.jackson.module.kogera.ReflectionCache
import io.github.projectmapk.jackson.module.kogera.annotation.JsonKUnbox
import io.github.projectmapk.jackson.module.kogera.isUnboxableValueClass
import io.github.projectmapk.jackson.module.kogera.reconstructClassOrNull
import io.github.projectmapk.jackson.module.kogera.ser.KotlinDurationValueToJavaDurationConverter
Expand Down Expand Up @@ -74,15 +74,15 @@ internal class KotlinFallbackAnnotationIntrospector(
override fun findSerializationConverter(a: Annotated): Converter<*, *>? = when (a) {
// Find a converter to handle the case where the getter returns an unboxed value from the value class.
is AnnotatedMethod -> cache.findBoxedReturnType(a.member)?.let {
if (useJavaDurationConversion && it == KotlinDuration::class.java) {
if (a.rawReturnType == KotlinDuration::class.java) {
if (useJavaDurationConversion && it == KOTLIN_DURATION_CLASS) {
if (a.rawReturnType == KOTLIN_DURATION_CLASS) {
KotlinToJavaDurationConverter
} else {
KotlinDurationValueToJavaDurationConverter
}
} else {
// If JsonUnbox is specified, the unboxed getter is used as is.
if (a.hasAnnotation(JsonKUnbox::class.java) || it.isAnnotationPresent(JsonKUnbox::class.java)) {
if (a.hasAnnotation(JSON_K_UNBOX_CLASS) || it.isAnnotationPresent(JSON_K_UNBOX_CLASS)) {
null
} else {
cache.getValueClassBoxConverter(a.rawReturnType, it)
Expand All @@ -95,7 +95,7 @@ internal class KotlinFallbackAnnotationIntrospector(

private fun lookupKotlinTypeConverter(a: AnnotatedClass) = when {
Sequence::class.java.isAssignableFrom(a.rawType) -> SequenceToIteratorConverter(a.type)
KotlinDuration::class.java == a.rawType -> KotlinToJavaDurationConverter.takeIf { useJavaDurationConversion }
KOTLIN_DURATION_CLASS == a.rawType -> KotlinToJavaDurationConverter.takeIf { useJavaDurationConversion }
else -> null
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import com.fasterxml.jackson.databind.introspect.AnnotatedMethod
import com.fasterxml.jackson.databind.introspect.AnnotatedParameter
import com.fasterxml.jackson.databind.introspect.NopAnnotationIntrospector
import com.fasterxml.jackson.databind.jsontype.NamedType
import io.github.projectmapk.jackson.module.kogera.JSON_PROPERTY_CLASS
import io.github.projectmapk.jackson.module.kogera.JmClass
import io.github.projectmapk.jackson.module.kogera.ReflectionCache
import io.github.projectmapk.jackson.module.kogera.hasCreatorAnnotation
Expand Down Expand Up @@ -42,7 +43,7 @@ internal class KotlinPrimaryAnnotationIntrospector(
// If JsonProperty.required is true, the behavior is clearly specified and the result is paramount.
// Otherwise, the required is determined from the configuration and the definition on Kotlin.
override fun hasRequiredMarker(m: AnnotatedMember): Boolean? {
val byAnnotation = _findAnnotation(m, JsonProperty::class.java)
val byAnnotation = _findAnnotation(m, JSON_PROPERTY_CLASS)
?.let { if (it.required) return true else false }

return cache.getJmClass(m.member.declaringClass)?.let {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -111,9 +111,6 @@ internal class ArgumentBucket(
// Since there is no multiple initialization in the use case, the key check is omitted.
arguments[index] = actualArg

val maskIndex = index / Integer.SIZE
masks[maskIndex] = masks[maskIndex] and BIT_FLAGS[index % Integer.SIZE]

masks.update(index, MaskOperation.SET)
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ internal class ConstructorValueCreator<T : Any>(
val parameterSize = it.size
val temp = it.copyOf(parameterSize + maskSize + 1)
for (i in 0 until maskSize) {
temp[it.size + i] = Int::class.javaPrimitiveType
temp[it.size + i] = Int::class.java
}
temp[parameterSize + maskSize] = defaultConstructorMarker

Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package io.github.projectmapk.jackson.module.kogera.deser.valueInstantiator.creator

import io.github.projectmapk.jackson.module.kogera.ANY_CLASS
import io.github.projectmapk.jackson.module.kogera.JmClass
import io.github.projectmapk.jackson.module.kogera.ReflectionCache
import io.github.projectmapk.jackson.module.kogera.call
Expand Down Expand Up @@ -50,9 +51,9 @@ internal class MethodValueCreator<T>(
temp[0] = companionObjectClass // companion object
parameterTypes.copyInto(temp, 1) // parameter types
for (i in (valueParameterSize + 1)..(valueParameterSize + maskSize)) { // masks
temp[i] = Int::class.javaPrimitiveType
temp[i] = Int::class.java
}
temp[valueParameterSize + maskSize + 1] = Object::class.java // maker
temp[valueParameterSize + maskSize + 1] = ANY_CLASS // maker
temp
} as Array<Class<*>>

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import com.fasterxml.jackson.databind.JavaType
import com.fasterxml.jackson.databind.type.TypeFactory
import com.fasterxml.jackson.databind.util.StdConverter
import io.github.projectmapk.jackson.module.kogera.JavaDuration
import io.github.projectmapk.jackson.module.kogera.KOTLIN_DURATION_CLASS
import io.github.projectmapk.jackson.module.kogera.KotlinDuration
import io.github.projectmapk.jackson.module.kogera.ValueClassBoxConverter
import kotlin.time.toJavaDuration
Expand All @@ -20,7 +21,7 @@ internal class SequenceToIteratorConverter(private val input: JavaType) : StdCon
}

internal object KotlinDurationValueToJavaDurationConverter : StdConverter<Long, JavaDuration>() {
private val boxConverter by lazy { ValueClassBoxConverter(Long::class.java, KotlinDuration::class.java) }
private val boxConverter by lazy { ValueClassBoxConverter(Long::class.java, KOTLIN_DURATION_CLASS) }

override fun convert(value: Long): JavaDuration = KotlinToJavaDurationConverter.convert(boxConverter.convert(value))
}
Expand Down

0 comments on commit a10665c

Please sign in to comment.