Skip to content
Merged
Show file tree
Hide file tree
Changes from 4 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,65 @@
package com.fasterxml.jackson.module.kotlin;

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;
import org.jetbrains.annotations.Nullable;

// Since there was no way to override Java getter in a user-friendly way, it is defined in Java. See KT-6653.
// 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(
@Nullable
String kotlinParameterName,
@NotNull
Class<?> valueClass,
@NotNull
JsonParser p,
@NotNull
String msg,
@NotNull
PropertyName pname
) {
super(p, msg, pname);
// Basically, this will never be null, but it is handled here to avoid errors in unusual cases.
this.kotlinPropertyName = kotlinParameterName == null ? "UNKNOWN" : 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 @@ -10,14 +10,10 @@ import kotlin.reflect.KParameter
* parameter was missing or null.
*/
@Deprecated(
"It is recommended that InvalidNullException be referenced when possible," +
" as the change is discussed for 2.20 and later." +
" See #617 for details.",
ReplaceWith(
"InvalidNullException",
"com.fasterxml.jackson.databind.exc.InvalidNullException"
),
DeprecationLevel.WARNING
"Since 2.20, this exception is no longer thrown and has been replaced by KotlinInvalidNullException. " +
"See #617 for details.",
ReplaceWith("KotlinInvalidNullException"),
DeprecationLevel.ERROR
)
// When deserialized by the JDK, the parameter property will be null, ignoring nullability.
// This is a temporary workaround for #572 and we will eventually remove this class.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -90,15 +90,18 @@ internal class KotlinValueInstantiator(
if (propType.requireEmptyValue()) {
paramVal = valueDeserializer!!.getEmptyValue(ctxt)
} else {
val pname = jsonProp.name
val isMissingAndRequired = isMissing && jsonProp.isRequired

// Since #310 reported that the calculation cost is high, isGenericTypeVar is determined last.
if (isMissingAndRequired || (!paramType.isMarkedNullable && !paramType.isGenericTypeVar())) {
throw MissingKotlinParameterException(
parameter = paramDef,
processor = ctxt.parser,
msg = "Instantiation of ${this.valueTypeDesc} value failed for JSON property ${jsonProp.name} due to missing (therefore NULL) value for creator parameter ${paramDef.name} which is a non-nullable type"
).wrapWithPath(this.valueClass, jsonProp.name)
throw KotlinInvalidNullException(
paramDef.name,
this.valueClass,
ctxt.parser,
"Instantiation of ${this.valueTypeDesc} value failed for JSON property $pname due to missing (therefore NULL) value for creator parameter ${paramDef.name} which is a non-nullable type",
jsonProp.fullName,
).wrapWithPath(this.valueClass, pname)
}
}
} else if (strictNullChecks) {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
package com.fasterxml.jackson.module.kotlin

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

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
Expand Up @@ -2,7 +2,7 @@ package com.fasterxml.jackson.module.kotlin.test

import com.fasterxml.jackson.databind.ObjectMapper
import com.fasterxml.jackson.module.kotlin.KotlinFeature.NullIsSameAsDefault
import com.fasterxml.jackson.module.kotlin.MissingKotlinParameterException
import com.fasterxml.jackson.module.kotlin.KotlinInvalidNullException
import com.fasterxml.jackson.module.kotlin.kotlinModule
import com.fasterxml.jackson.module.kotlin.readValue
import org.junit.jupiter.api.Assertions.*
Expand Down Expand Up @@ -142,7 +142,7 @@ class TestNullToDefault {

@Test
fun shouldThrowExceptionWhenProvidedNullForNotNullFieldWithoutDefault() {
assertThrows<MissingKotlinParameterException> {
assertThrows<KotlinInvalidNullException> {
createMapper(true).readValue<TestClass>(
"""{
"text": null
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
package com.fasterxml.jackson.module.kotlin.test.github

import com.fasterxml.jackson.annotation.JsonProperty
import com.fasterxml.jackson.module.kotlin.MissingKotlinParameterException
import com.fasterxml.jackson.module.kotlin.KotlinInvalidNullException
import com.fasterxml.jackson.module.kotlin.defaultMapper
import com.fasterxml.jackson.module.kotlin.readValue
import org.junit.jupiter.api.Test
Expand All @@ -20,7 +20,7 @@ class TestGithub168 {

@Test
fun testIfRequiredIsReallyRequiredWhenAbsent() {
assertThrows<MissingKotlinParameterException> {
assertThrows<KotlinInvalidNullException> {
val obj = defaultMapper.readValue<TestClass>("""{"baz":"whatever"}""")
assertEquals("whatever", obj.baz)
}
Expand Down
Loading