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
1 change: 1 addition & 0 deletions release-notes/CREDITS-2.x
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ Contributors:
# 2.20.0 (not yet released)

WrongWrong (@k163377)
* #1025: Deprecate MissingKotlinParameterException and replace with new exception
* #1020: Fixed old StrictNullChecks to throw exceptions similar to those thrown by new StrictNullChecks
* #1018: Use MethodHandle in processing related to value class
* #969: Cleanup of deprecated contents
Expand Down
5 changes: 5 additions & 0 deletions release-notes/VERSION-2.x
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,11 @@ Co-maintainers:
------------------------------------------------------------------------

2.20.0 (not yet released)
#1025: When a null is entered for a non-null parameter, the KotlinInvalidNullException is now thrown instead of the
deprecated MissingKotlinParameterException.
The new exception is a subclass of InvalidNullException.
See the comment below for information contained in this exception.
https://github.com/FasterXML/jackson-module-kotlin/issues/617#issuecomment-3124423585
#1020: Exceptions thrown by the old StrictNullChecks are now the similar to the new StrictNullChecks.
This means that the old StrictNullChecks will no longer throw MissingKotlinParameterException.
See PR for what is thrown and how error messages change.
Expand Down
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;

// 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(
@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 @@ -5,6 +5,7 @@ import kotlin.test.assertNotNull
import kotlin.test.assertNull

class MissingKotlinParameterExceptionTest {
@Suppress("DEPRECATION_ERROR")
@Test
fun jdkSerializabilityTest() {
val param = ::MissingKotlinParameterException.parameters.first()
Expand Down
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
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.databind.JsonMappingException
import com.fasterxml.jackson.databind.exc.MismatchedInputException
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.Assertions.assertEquals
Expand All @@ -19,8 +19,8 @@ private class TestGithub32 {
}

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

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

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

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

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