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
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
package io.github.projectmapk.jackson.module.kogera;

import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.databind.JsonMappingException;
import com.fasterxml.jackson.databind.PropertyName;
import com.fasterxml.jackson.databind.exc.InvalidNullException;
import org.jetbrains.annotations.NotNull;

// Due to a limitation in KT-6653, there is no user-friendly way to override Java getters in Kotlin.
// The reason for not having detailed information(e.g. KParameter) is to keep the class Serializable.
/**
* Specialized {@link JsonMappingException} sub-class used to indicate that a mandatory Kotlin creator parameter was
* missing or null.
*/
public final class KotlinInvalidNullException extends InvalidNullException {
@NotNull
private final String kotlinPropertyName;

KotlinInvalidNullException(
@NotNull
String kotlinParameterName,
@NotNull
Class<?> valueClass,
@NotNull
JsonParser p,
@NotNull
String msg,
@NotNull
PropertyName pname
) {
super(p, msg, pname);
this.kotlinPropertyName = kotlinParameterName;
this._targetType = valueClass;
}

/**
* @return Parameter name in Kotlin.
*/
@NotNull
public String getKotlinPropertyName() {
return kotlinPropertyName;
}

// region: Override getters to make nullability explicit and to explain its role in this class.
/**
* @return Parameter name in Jackson.
*/
@NotNull
@Override
public PropertyName getPropertyName() {
return super.getPropertyName();
}

/**
* @return The {@link Class} object representing the class that declares the creator.
*/
@NotNull
@Override
public Class<?> getTargetType() {
return super.getTargetType();
}
// endregion
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ package io.github.projectmapk.jackson.module.kogera

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 io.github.projectmapk.jackson.module.kogera.annotation.JsonKUnbox
import java.lang.invoke.MethodHandle
import java.lang.invoke.MethodHandles
Expand Down Expand Up @@ -126,3 +128,12 @@ internal fun Method.toSignature(): JvmMethodSignature = JvmMethodSignature(
this.name,
parameterTypes.toDescBuilder().appendDescriptor(this.returnType).toString(),
)

// Delegate for calling package-private constructor
internal fun kotlinInvalidNullException(
kotlinParameterName: String,
valueClass: Class<*>,
p: JsonParser,
msg: String,
pname: PropertyName,
) = KotlinInvalidNullException(kotlinParameterName, valueClass, p, msg, pname)
Original file line number Diff line number Diff line change
Expand Up @@ -11,13 +11,13 @@ import com.fasterxml.jackson.databind.deser.SettableBeanProperty
import com.fasterxml.jackson.databind.deser.ValueInstantiator
import com.fasterxml.jackson.databind.deser.impl.PropertyValueBuffer
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.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
import io.github.projectmapk.jackson.module.kogera.kotlinInvalidNullException
import java.lang.reflect.Constructor
import java.lang.reflect.Executable
import java.lang.reflect.Method
Expand Down Expand Up @@ -99,9 +99,19 @@ internal class KotlinValueInstantiator(
} else {
val isMissingAndRequired = isMissing && jsonProp.isRequired
if (isMissingAndRequired || !(paramDef.isNullable || paramDef.isGenericType)) {
throw InvalidNullException
.from(ctxt, jsonProp.fullName, jsonProp.type)
.wrapWithPath(this.valueClass, jsonProp.name)
val kotlinParameterName = paramDef.name
val pname = jsonProp.name
val message = "Instantiation of ${this.valueTypeDesc} value failed for JSON property" +
" $pname due to missing (therefore NULL) value for creator parameter" +
" $kotlinParameterName which is a non-nullable type"

throw kotlinInvalidNullException(
kotlinParameterName,
this.valueClass,
ctxt.parser,
message,
jsonProp.fullName,
).wrapWithPath(this.valueClass, pname)
}
}
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
package io.github.projectmapk.jackson.module.kogera

import com.fasterxml.jackson.annotation.JsonProperty
import org.junit.jupiter.api.Assertions.assertEquals
import org.junit.jupiter.api.Test
import org.junit.jupiter.api.assertThrows

private data class Dto(
val foo: String,
@JsonProperty("bar")
val _bar: String,
)

class KotlinInvalidNullExceptionTest {
@Test
fun fooTest() {
val json = """{"bar":"bar"}"""
val ex = assertThrows<KotlinInvalidNullException> { defaultMapper.readValue<Dto>(json) }

assertEquals("foo", ex.kotlinPropertyName)
assertEquals("foo", ex.propertyName.simpleName)
assertEquals(Dto::class, ex.targetType.kotlin)
}

@Test
fun barTest() {
val json = """{"foo":"foo","bar":null}"""
val ex = assertThrows<KotlinInvalidNullException> { defaultMapper.readValue<Dto>(json) }

assertEquals("_bar", ex.kotlinPropertyName)
assertEquals("bar", ex.propertyName.simpleName)
assertEquals(Dto::class, ex.targetType.kotlin)
}
}
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
package io.github.projectmapk.jackson.module.kogera.zPorted.test

import com.fasterxml.jackson.databind.ObjectMapper
import com.fasterxml.jackson.databind.exc.InvalidNullException
import io.github.projectmapk.jackson.module.kogera.KotlinFeature.NullIsSameAsDefault
import io.github.projectmapk.jackson.module.kogera.KotlinInvalidNullException
import io.github.projectmapk.jackson.module.kogera.kotlinModule
import io.github.projectmapk.jackson.module.kogera.readValue
import org.junit.jupiter.api.Assertions.assertTrue
Expand Down Expand Up @@ -55,7 +55,7 @@ class TestNullToDefault {

@Test
fun shouldNotUseNullAsDefault() {
assertThrows<InvalidNullException> {
assertThrows<KotlinInvalidNullException> {
createMapper(false).readValue<TestClass>(
"""{
"sku": "974",
Expand All @@ -68,10 +68,9 @@ class TestNullToDefault {
}
}

// @Test(expected = MissingKotlinParameterException::class)
@Test
fun errorIfNotDefault() {
assertThrows<InvalidNullException> {
assertThrows<KotlinInvalidNullException> {
createMapper(true).readValue<TestClass>(
"""{
"sku": "974",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ package io.github.projectmapk.jackson.module.kogera.zPorted.test.github
import com.fasterxml.jackson.annotation.JsonInclude
import com.fasterxml.jackson.annotation.JsonProperty
import com.fasterxml.jackson.annotation.OptBoolean
import com.fasterxml.jackson.databind.exc.InvalidNullException
import io.github.projectmapk.jackson.module.kogera.KotlinInvalidNullException
import io.github.projectmapk.jackson.module.kogera.jacksonObjectMapper
import io.github.projectmapk.jackson.module.kogera.readValue
import org.junit.jupiter.api.Assertions.assertEquals
Expand All @@ -20,7 +20,7 @@ class GitHub917 {
val value = Failing<String?>(null)
val json = mapper.writeValueAsString(value)

assertThrows<InvalidNullException> {
assertThrows<KotlinInvalidNullException> {
val deserializedValue = mapper.readValue<Failing<String?>>(json)
assertEquals(value ,deserializedValue)
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
package io.github.projectmapk.jackson.module.kogera.zPorted.test.github

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

@Test
fun testIfRequiredIsReallyRequiredWhenAbsent() {
assertThrows<InvalidNullException> {
assertThrows<KotlinInvalidNullException> {
defaultMapper.readValue<TestClass>("""{"baz":"whatever"}""")
}
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
package io.github.projectmapk.jackson.module.kogera.zPorted.test.github

import com.fasterxml.jackson.databind.JsonMappingException
import com.fasterxml.jackson.databind.exc.InvalidNullException
import io.github.projectmapk.jackson.module.kogera.KotlinInvalidNullException
import io.github.projectmapk.jackson.module.kogera.defaultMapper
import io.github.projectmapk.jackson.module.kogera.readValue
import org.junit.jupiter.api.Assertions.assertEquals
Expand All @@ -22,8 +22,8 @@ private class TestGithub32 {
}

@Test fun `missing mandatory data class constructor param`() {
val thrown = assertThrows<InvalidNullException>(
"MissingKotlinParameterException with missing `firstName` parameter"
val thrown = assertThrows<KotlinInvalidNullException>(
"KotlinInvalidNullException with missing `firstName` parameter"
) {
defaultMapper.readValue<Person>(
"""
Expand All @@ -40,7 +40,7 @@ private class TestGithub32 {
}

@Test fun `null mandatory data class constructor param`() {
val thrown = assertThrows<InvalidNullException> {
val thrown = assertThrows<KotlinInvalidNullException> {
defaultMapper.readValue<Person>(
"""
{
Expand All @@ -57,7 +57,7 @@ private class TestGithub32 {
}

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

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

@Test fun `missing mandatory constructor param - nested in class with List arg constructor`() {
val thrown = assertThrows<InvalidNullException> {
val thrown = assertThrows<KotlinInvalidNullException> {
defaultMapper.readValue<Crowd>(
"""
{
Expand Down
Loading