jackson-module-kotlin
jackson-module-kotlin copied to clipboard
MismatchedInputException on single ctor paramater, but not with multiple
Describe the bug
Deserialization fails for data class Data(var A: Int), probably because of casing problems, (although I couldn't find what the default behavior should be), which can be fixed by using a lowercase for A, or by using different propertyNamingStrategies, like setPropertyNamingStrategy(PropertyNamingStrategy.SNAKE_CASE).
But it strangely doesn't fail for data class Data(var A: Int, var b: Int) and for data class Data(var A: Int, var B: Int) and for data class Data(var a: Int, var B: Int). So when a second constructor parameter is present, the behavior differs unexpectedly. So then somehow, the casing for A is ignored, whereas it isn't when only a single constructor parameter is present.
To Reproduce
internal class ObjectMapperTests {
private val mapper = ObjectMapper()
.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false)
.registerModule(KotlinModule())
val json = """{"A":1,"B":2}"""
@Test // succeeds
fun upperAUpperB() {
data class Data(var A: Int, var B: Int)
mapper.readValue<Data>(json)
}
@Test // succeeds
fun upperALowerB() {
data class Data(var A: Int, var b: Int)
mapper.readValue<Data>(json)
}
@Test // succeeds
fun lowerAUpperB() {
data class Data(var a: Int, var B: Int)
mapper.readValue<Data>(json)
}
@Test // succeeds
fun lowerA() {
data class Data(var a: Int)
mapper.readValue<Data>(json)
}
@Test // succeeds
fun lowerB() {
data class Data(var b: Int)
mapper.readValue<Data>(json)
}
@Test // fails
fun upperA() {
data class Data(var A: Int)
mapper.readValue<Data>(json)
}
@Test // Fails
fun upperB() {
data class Data(var B: Int)
mapper.readValue<Data>(json)
}
}
Versions Kotlin: 1.5.10 Jackson-module-kotlin: 2.12.5
Expected behavior
If data class Data(var A: Int) would fail (which it does), I would expect data class Data(var A: Int, var B: Int) to also fail, but it does not. So somehow the propertyNamingStrategy seems to differ when a second parameter is present.
Update: Here some more examples of different behavior:
val mapper = ObjectMapper()
.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false)
.registerKotlinModule()
@Test // ok
fun lowercaseJsonStringProperty() {
val json = """{"abc":"hi"}"""
data class Data(val abc: String)
mapper.readValue<Data>(json)
}
@Test // fails
fun uppercaseJsonStringProperty() {
val json = """{"Abc":"hi"}"""
data class Data(val abc: String)
mapper.readValue<Data>(json)
}
@Test // ok
fun uppercaseJsonIntProperty() {
val json = """{"Abc":1}"""
data class Data(var abc: Int)
mapper.readValue<Data>(json)
}
If lowercaseJsonStringProperty succeeds and uppercaseJsonStringProperty fails, then you would expect uppercaseJsonIntProperty to also fail, but it does not.
Well this is interesting…I just closed #499 which is very similar. I thought it was simply odd usage that never worked, but with this report I think this could be a real issue.
I suspect this could be related to the perennial "1-argument constructors are ambiguous" problem?
Although with NamingStrategy there is also the possibility of problems (from databind) wrt too-late binding of constructor properties. :-/
This affects all 2.12.x versions
Got interrupted looking at this, but I think @cowtowncoder is right, this isn't a new issue. I'll look through existing issues that relate to this later (or @jtdev you could take a glance https://github.com/FasterXML/jackson-module-kotlin/issues?q=is%3Aissue+is%3Aopen+1-argument+constructor)
Either way a workaround is to add @JsonProperty("foo") annotations to constructor params.
As previously confirmed, this is a problem caused by databind, and the same problem can be reproduced with Java only.
import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.junit.Test;
public class GitHub498J {
public static class UpperA {
private final int A;
public UpperA(int A) {
this.A = A;
}
public int getA() {
return A;
}
// @JsonCreator(mode = JsonCreator.Mode.PROPERTIES)
public static UpperA creator(@JsonProperty("A") int A) {
return new UpperA(A);
}
}
@Test
public void test() throws JsonProcessingException {
ObjectMapper mapper = new ObjectMapper().configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
UpperA result = mapper.readValue("{\"A\":1}", UpperA.class);
System.out.println(result.getA());
}
}
Another workaround is to use @JsonCreator(mode = JsonCreator.Mode.PROPERTIES).
This issue is closed because it is not a kotlin-module issue.