Skip to content

Commit 019f99d

Browse files
authored
Merge pull request #378 from ProjectMapK/develop
Release 2025-08-09 10:21:16 +0000
2 parents 0eee4dd + 98c0255 commit 019f99d

File tree

9 files changed

+137
-20
lines changed

9 files changed

+137
-20
lines changed

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}-beta27"
21+
version = "${jacksonVersion}-beta28"
2222

2323
repositories {
2424
mavenCentral()
Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
package io.github.projectmapk.jackson.module.kogera;
2+
3+
import com.fasterxml.jackson.core.JsonParser;
4+
import com.fasterxml.jackson.databind.JsonMappingException;
5+
import com.fasterxml.jackson.databind.PropertyName;
6+
import com.fasterxml.jackson.databind.exc.InvalidNullException;
7+
import org.jetbrains.annotations.NotNull;
8+
9+
// Due to a limitation in KT-6653, there is no user-friendly way to override Java getters in Kotlin.
10+
// The reason for not having detailed information(e.g. KParameter) is to keep the class Serializable.
11+
/**
12+
* Specialized {@link JsonMappingException} sub-class used to indicate that a mandatory Kotlin creator parameter was
13+
* missing or null.
14+
*/
15+
public final class KotlinInvalidNullException extends InvalidNullException {
16+
@NotNull
17+
private final String kotlinPropertyName;
18+
19+
KotlinInvalidNullException(
20+
@NotNull
21+
String kotlinParameterName,
22+
@NotNull
23+
Class<?> valueClass,
24+
@NotNull
25+
JsonParser p,
26+
@NotNull
27+
String msg,
28+
@NotNull
29+
PropertyName pname
30+
) {
31+
super(p, msg, pname);
32+
this.kotlinPropertyName = kotlinParameterName;
33+
this._targetType = valueClass;
34+
}
35+
36+
/**
37+
* @return Parameter name in Kotlin.
38+
*/
39+
@NotNull
40+
public String getKotlinPropertyName() {
41+
return kotlinPropertyName;
42+
}
43+
44+
// region: Override getters to make nullability explicit and to explain its role in this class.
45+
/**
46+
* @return Parameter name in Jackson.
47+
*/
48+
@NotNull
49+
@Override
50+
public PropertyName getPropertyName() {
51+
return super.getPropertyName();
52+
}
53+
54+
/**
55+
* @return The {@link Class} object representing the class that declares the creator.
56+
*/
57+
@NotNull
58+
@Override
59+
public Class<?> getTargetType() {
60+
return super.getTargetType();
61+
}
62+
// endregion
63+
}

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

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@ package io.github.projectmapk.jackson.module.kogera
22

33
import com.fasterxml.jackson.annotation.JsonCreator
44
import com.fasterxml.jackson.annotation.JsonProperty
5+
import com.fasterxml.jackson.core.JsonParser
6+
import com.fasterxml.jackson.databind.PropertyName
57
import io.github.projectmapk.jackson.module.kogera.annotation.JsonKUnbox
68
import java.lang.invoke.MethodHandle
79
import java.lang.invoke.MethodHandles
@@ -126,3 +128,12 @@ internal fun Method.toSignature(): JvmMethodSignature = JvmMethodSignature(
126128
this.name,
127129
parameterTypes.toDescBuilder().appendDescriptor(this.returnType).toString(),
128130
)
131+
132+
// Delegate for calling package-private constructor
133+
internal fun kotlinInvalidNullException(
134+
kotlinParameterName: String,
135+
valueClass: Class<*>,
136+
p: JsonParser,
137+
msg: String,
138+
pname: PropertyName,
139+
) = KotlinInvalidNullException(kotlinParameterName, valueClass, p, msg, pname)

src/main/kotlin/io/github/projectmapk/jackson/module/kogera/deser/valueInstantiator/KotlinValueInstantiator.kt

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -11,13 +11,13 @@ import com.fasterxml.jackson.databind.deser.SettableBeanProperty
1111
import com.fasterxml.jackson.databind.deser.ValueInstantiator
1212
import com.fasterxml.jackson.databind.deser.impl.PropertyValueBuffer
1313
import com.fasterxml.jackson.databind.deser.std.StdValueInstantiator
14-
import com.fasterxml.jackson.databind.exc.InvalidNullException
1514
import com.fasterxml.jackson.databind.module.SimpleValueInstantiators
1615
import io.github.projectmapk.jackson.module.kogera.ReflectionCache
1716
import io.github.projectmapk.jackson.module.kogera.deser.WrapsNullableValueClassDeserializer
1817
import io.github.projectmapk.jackson.module.kogera.deser.valueInstantiator.creator.ConstructorValueCreator
1918
import io.github.projectmapk.jackson.module.kogera.deser.valueInstantiator.creator.MethodValueCreator
2019
import io.github.projectmapk.jackson.module.kogera.deser.valueInstantiator.creator.ValueCreator
20+
import io.github.projectmapk.jackson.module.kogera.kotlinInvalidNullException
2121
import java.lang.reflect.Constructor
2222
import java.lang.reflect.Executable
2323
import java.lang.reflect.Method
@@ -99,9 +99,19 @@ internal class KotlinValueInstantiator(
9999
} else {
100100
val isMissingAndRequired = isMissing && jsonProp.isRequired
101101
if (isMissingAndRequired || !(paramDef.isNullable || paramDef.isGenericType)) {
102-
throw InvalidNullException
103-
.from(ctxt, jsonProp.fullName, jsonProp.type)
104-
.wrapWithPath(this.valueClass, jsonProp.name)
102+
val kotlinParameterName = paramDef.name
103+
val pname = jsonProp.name
104+
val message = "Instantiation of ${this.valueTypeDesc} value failed for JSON property" +
105+
" $pname due to missing (therefore NULL) value for creator parameter" +
106+
" $kotlinParameterName which is a non-nullable type"
107+
108+
throw kotlinInvalidNullException(
109+
kotlinParameterName,
110+
this.valueClass,
111+
ctxt.parser,
112+
message,
113+
jsonProp.fullName,
114+
).wrapWithPath(this.valueClass, pname)
105115
}
106116
}
107117
}
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
package io.github.projectmapk.jackson.module.kogera
2+
3+
import com.fasterxml.jackson.annotation.JsonProperty
4+
import org.junit.jupiter.api.Assertions.assertEquals
5+
import org.junit.jupiter.api.Test
6+
import org.junit.jupiter.api.assertThrows
7+
8+
private data class Dto(
9+
val foo: String,
10+
@JsonProperty("bar")
11+
val _bar: String,
12+
)
13+
14+
class KotlinInvalidNullExceptionTest {
15+
@Test
16+
fun fooTest() {
17+
val json = """{"bar":"bar"}"""
18+
val ex = assertThrows<KotlinInvalidNullException> { defaultMapper.readValue<Dto>(json) }
19+
20+
assertEquals("foo", ex.kotlinPropertyName)
21+
assertEquals("foo", ex.propertyName.simpleName)
22+
assertEquals(Dto::class, ex.targetType.kotlin)
23+
}
24+
25+
@Test
26+
fun barTest() {
27+
val json = """{"foo":"foo","bar":null}"""
28+
val ex = assertThrows<KotlinInvalidNullException> { defaultMapper.readValue<Dto>(json) }
29+
30+
assertEquals("_bar", ex.kotlinPropertyName)
31+
assertEquals("bar", ex.propertyName.simpleName)
32+
assertEquals(Dto::class, ex.targetType.kotlin)
33+
}
34+
}

src/test/kotlin/io/github/projectmapk/jackson/module/kogera/zPorted/test/NullToDefaultTests.kt

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
package io.github.projectmapk.jackson.module.kogera.zPorted.test
22

33
import com.fasterxml.jackson.databind.ObjectMapper
4-
import com.fasterxml.jackson.databind.exc.InvalidNullException
54
import io.github.projectmapk.jackson.module.kogera.KotlinFeature.NullIsSameAsDefault
5+
import io.github.projectmapk.jackson.module.kogera.KotlinInvalidNullException
66
import io.github.projectmapk.jackson.module.kogera.kotlinModule
77
import io.github.projectmapk.jackson.module.kogera.readValue
88
import org.junit.jupiter.api.Assertions.assertTrue
@@ -55,7 +55,7 @@ class TestNullToDefault {
5555

5656
@Test
5757
fun shouldNotUseNullAsDefault() {
58-
assertThrows<InvalidNullException> {
58+
assertThrows<KotlinInvalidNullException> {
5959
createMapper(false).readValue<TestClass>(
6060
"""{
6161
"sku": "974",
@@ -68,10 +68,9 @@ class TestNullToDefault {
6868
}
6969
}
7070

71-
// @Test(expected = MissingKotlinParameterException::class)
7271
@Test
7372
fun errorIfNotDefault() {
74-
assertThrows<InvalidNullException> {
73+
assertThrows<KotlinInvalidNullException> {
7574
createMapper(true).readValue<TestClass>(
7675
"""{
7776
"sku": "974",

src/test/kotlin/io/github/projectmapk/jackson/module/kogera/zPorted/test/github/GitHub917.kt

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ package io.github.projectmapk.jackson.module.kogera.zPorted.test.github
33
import com.fasterxml.jackson.annotation.JsonInclude
44
import com.fasterxml.jackson.annotation.JsonProperty
55
import com.fasterxml.jackson.annotation.OptBoolean
6-
import com.fasterxml.jackson.databind.exc.InvalidNullException
6+
import io.github.projectmapk.jackson.module.kogera.KotlinInvalidNullException
77
import io.github.projectmapk.jackson.module.kogera.jacksonObjectMapper
88
import io.github.projectmapk.jackson.module.kogera.readValue
99
import org.junit.jupiter.api.Assertions.assertEquals
@@ -20,7 +20,7 @@ class GitHub917 {
2020
val value = Failing<String?>(null)
2121
val json = mapper.writeValueAsString(value)
2222

23-
assertThrows<InvalidNullException> {
23+
assertThrows<KotlinInvalidNullException> {
2424
val deserializedValue = mapper.readValue<Failing<String?>>(json)
2525
assertEquals(value ,deserializedValue)
2626
}

src/test/kotlin/io/github/projectmapk/jackson/module/kogera/zPorted/test/github/Github168.kt

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
package io.github.projectmapk.jackson.module.kogera.zPorted.test.github
22

33
import com.fasterxml.jackson.annotation.JsonProperty
4-
import com.fasterxml.jackson.databind.exc.InvalidNullException
4+
import io.github.projectmapk.jackson.module.kogera.KotlinInvalidNullException
55
import io.github.projectmapk.jackson.module.kogera.defaultMapper
66
import io.github.projectmapk.jackson.module.kogera.readValue
77
import org.junit.jupiter.api.Assertions.assertEquals
@@ -20,7 +20,7 @@ class TestGithub168 {
2020

2121
@Test
2222
fun testIfRequiredIsReallyRequiredWhenAbsent() {
23-
assertThrows<InvalidNullException> {
23+
assertThrows<KotlinInvalidNullException> {
2424
defaultMapper.readValue<TestClass>("""{"baz":"whatever"}""")
2525
}
2626
}

src/test/kotlin/io/github/projectmapk/jackson/module/kogera/zPorted/test/github/Github32.kt

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
package io.github.projectmapk.jackson.module.kogera.zPorted.test.github
22

33
import com.fasterxml.jackson.databind.JsonMappingException
4-
import com.fasterxml.jackson.databind.exc.InvalidNullException
4+
import io.github.projectmapk.jackson.module.kogera.KotlinInvalidNullException
55
import io.github.projectmapk.jackson.module.kogera.defaultMapper
66
import io.github.projectmapk.jackson.module.kogera.readValue
77
import org.junit.jupiter.api.Assertions.assertEquals
@@ -22,8 +22,8 @@ private class TestGithub32 {
2222
}
2323

2424
@Test fun `missing mandatory data class constructor param`() {
25-
val thrown = assertThrows<InvalidNullException>(
26-
"MissingKotlinParameterException with missing `firstName` parameter"
25+
val thrown = assertThrows<KotlinInvalidNullException>(
26+
"KotlinInvalidNullException with missing `firstName` parameter"
2727
) {
2828
defaultMapper.readValue<Person>(
2929
"""
@@ -40,7 +40,7 @@ private class TestGithub32 {
4040
}
4141

4242
@Test fun `null mandatory data class constructor param`() {
43-
val thrown = assertThrows<InvalidNullException> {
43+
val thrown = assertThrows<KotlinInvalidNullException> {
4444
defaultMapper.readValue<Person>(
4545
"""
4646
{
@@ -57,7 +57,7 @@ private class TestGithub32 {
5757
}
5858

5959
@Test fun `missing mandatory constructor param - nested in class with default constructor`() {
60-
val thrown = assertThrows<InvalidNullException> {
60+
val thrown = assertThrows<KotlinInvalidNullException> {
6161
defaultMapper.readValue<WrapperWithDefaultContructor>(
6262
"""
6363
{
@@ -75,7 +75,7 @@ private class TestGithub32 {
7575
}
7676

7777
@Test fun `missing mandatory constructor param - nested in class with single arg constructor`() {
78-
val thrown = assertThrows<InvalidNullException> {
78+
val thrown = assertThrows<KotlinInvalidNullException> {
7979
defaultMapper.readValue<WrapperWithArgsContructor>(
8080
"""
8181
{
@@ -93,7 +93,7 @@ private class TestGithub32 {
9393
}
9494

9595
@Test fun `missing mandatory constructor param - nested in class with List arg constructor`() {
96-
val thrown = assertThrows<InvalidNullException> {
96+
val thrown = assertThrows<KotlinInvalidNullException> {
9797
defaultMapper.readValue<Crowd>(
9898
"""
9999
{

0 commit comments

Comments
 (0)