Skip to content

Support for value classes (2) #694

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 3 commits into from
Closed
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
7 changes: 6 additions & 1 deletion pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -81,8 +81,9 @@

<dependencies>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<groupId>com.github.wplong11</groupId>
<artifactId>jackson-databind</artifactId>
<version>02f3fd2988</version>
</dependency>

<dependency>
Expand Down Expand Up @@ -253,5 +254,9 @@
<enabled>true</enabled>
</snapshots>
</repository>
<repository>
<id>jitpack.io</id>
<url>https://jitpack.io</url>
</repository>
</repositories>
</project>
Original file line number Diff line number Diff line change
Expand Up @@ -159,6 +159,10 @@ internal class KotlinAnnotationIntrospector(private val context: Module.SetupCon
private fun KFunction<*>.isSetterLike(): Boolean = parameters.size == 2 && returnType == UNIT_TYPE

private fun AnnotatedParameter.hasRequiredMarker(): Boolean? {
if (rawType.isKotlinDefaultConstructorMarker) {
return false
}

val member = this.member
val byAnnotation = this.getAnnotation(JsonProperty::class.java)?.required

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,16 +2,12 @@ package com.fasterxml.jackson.module.kotlin

import com.fasterxml.jackson.annotation.JsonCreator
import com.fasterxml.jackson.annotation.JsonProperty
import com.fasterxml.jackson.databind.PropertyName
import com.fasterxml.jackson.databind.cfg.MapperConfig
import com.fasterxml.jackson.databind.introspect.Annotated
import com.fasterxml.jackson.databind.introspect.AnnotatedConstructor
import com.fasterxml.jackson.databind.introspect.AnnotatedField
import com.fasterxml.jackson.databind.introspect.AnnotatedMember
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.BeanUtil
import java.lang.reflect.Constructor
import java.lang.reflect.Method
import java.util.Locale
Expand Down Expand Up @@ -67,6 +63,10 @@ internal class KotlinNamesAnnotationIntrospector(

// since 2.4
override fun findImplicitPropertyName(member: AnnotatedMember): String? {
if (member.rawType.isKotlinDefaultConstructorMarker) {
return "DefaultConstructorMarker"
}

if (!member.declaringClass.isKotlinClass()) return null

return when (member) {
Expand Down Expand Up @@ -118,23 +118,17 @@ internal class KotlinNamesAnnotationIntrospector(
if (member is AnnotatedConstructor && member.isKotlinConstructorWithParameters())
cache.checkConstructorIsCreatorAnnotated(member) { hasCreatorAnnotation(it) }
else
false
member.isJvmInlineClassSyntheticBoxingFunction

private val Annotated.isJvmInlineClassSyntheticBoxingFunction: Boolean
get() = name == "box-impl"

@Suppress("UNCHECKED_CAST")
private fun findKotlinParameterName(param: AnnotatedParameter): String? {
return if (param.declaringClass.isKotlinClass()) {
val member = param.owner.member
if (member is Constructor<*>) {
val ctor = (member as Constructor<Any>)
val ctorParmCount = ctor.parameterTypes.size
val ktorParmCount = try { ctor.kotlinFunction?.parameters?.size ?: 0 }
catch (ex: KotlinReflectionInternalError) { 0 }
catch (ex: UnsupportedOperationException) { 0 }
if (ktorParmCount > 0 && ktorParmCount == ctorParmCount) {
ctor.kotlinFunction?.parameters?.get(param.index)?.name
} else {
null
}
member.kotlinFunction?.parameters?.elementAtOrNull(param.index)?.name
} else if (member is Method) {
try {
val temp = member.kotlinFunction
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -138,6 +138,16 @@ internal class KotlinValueInstantiator(
numCallableParameters++
}

val isJvmInlineClassSyntheticConstructor: Boolean =
numCallableParameters == jsonParamValueList.size - 1
&& valueCreator is ConstructorValueCreator
&& props.last().type.rawClass.isKotlinDefaultConstructorMarker
if (isJvmInlineClassSyntheticConstructor) {
jsonParamValueList[numCallableParameters] = null
callableParameters[numCallableParameters] = null
numCallableParameters++
}

return if (numCallableParameters == jsonParamValueList.size && valueCreator is ConstructorValueCreator) {
// we didn't do anything special with default parameters, do a normal call
super.createFromObjectWith(ctxt, jsonParamValueList)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package com.fasterxml.jackson.module.kotlin

/**
* Identification class for synthetic constructor generated for default arguments and value classes.
*/
private val DEFAULT_CONSTRUCTOR_MARKER: Class<*> = try {
Class.forName("kotlin.jvm.internal.DefaultConstructorMarker")
} catch (ex: ClassNotFoundException) {
throw IllegalStateException(
"DefaultConstructorMarker not on classpath. Make sure the Kotlin stdlib is on the classpath."
)
}

val Class<*>.isKotlinDefaultConstructorMarker: Boolean
get() = this == DEFAULT_CONSTRUCTOR_MARKER
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
package com.fasterxml.jackson.module.kotlin.test

import com.fasterxml.jackson.annotation.JsonProperty
import com.fasterxml.jackson.databind.ObjectMapper
import com.fasterxml.jackson.module.kotlin.kotlinModule
import com.fasterxml.jackson.module.kotlin.readValue
import org.junit.Assert
import org.junit.Test

class ValueClassTest {
@JvmInline
value class UserId(val rawValue: String)

data class User(
val id: UserId,
val name: String,
)

data class Customer(
@JsonProperty("customerId")
val id: UserId,
@JsonProperty("customerName")
val name: String,
)

@Test
fun `deserialize value class correctly`() {
val json: String = """
"1111"
""".trimIndent()

val deserialized: UserId = createMapper().readValue(json)

Assert.assertEquals("1111", deserialized.rawValue)
}

@Test
fun `deserialize complex object with value class`() {
val json: String = """
{
"id": "1111",
"name": "foo"
}
""".trimIndent()

val deserialized: User = createMapper().readValue(json)

Assert.assertEquals("1111", deserialized.id.rawValue)
}

@Test
fun `deserialize complex annotated object with value class`() {
val json: String = """
{
"customerId": "1111",
"customerName": "foo"
}
""".trimIndent()

val deserialized: Customer = createMapper().readValue(json)

Assert.assertEquals("1111", deserialized.id.rawValue)
}

private fun createMapper(): ObjectMapper {
return ObjectMapper().registerModule(kotlinModule())
}
}