Description
I have a weird issue using @JsonCreator
on enums, but only when using an ObjectMapper
with customized visibility. Depending on the name of the parameters of the @JsonCreator
and constructor methods, the deserialization will fail or succeed. Tested using Jackson 2.8.7 with Kotlin 1.1.2, adding kotlin-reflect
or not does not change the results.
Short snippet to reproduce the issue: those 2 classes are exactly the same, except for the name of fromInt
parameter (JacksonTest
class uses the same name as the enum constructor, while JacksonTest2
uses a different name)
enum class JacksonTest(private val value: Int) {
TEST(0), TEST2(1);
companion object {
@JvmStatic @JsonCreator
fun fromInt(value: Int): JacksonTest {
return JacksonTest.values().find { it.value == value } ?: JacksonTest.TEST
}
}
}
enum class JacksonTest2(private val value: Int) {
TEST(0), TEST2(1);
companion object {
@JvmStatic @JsonCreator
fun fromInt(intValue: Int): JacksonTest2 {
return JacksonTest2.values().find { it.value == intValue } ?: JacksonTest2.TEST
}
}
}
Then running the following tests, the first one fails when using custom visibility on ObjectMapper, but only when using JacksonTest
enum, it works well with JacksonTest2
(which is exactly the same class, with just a renamed parameter):
@Test
fun jacksonTest1_customMapper_fails() {
val mapper = ObjectMapper()
mapper.registerModule(KotlinModule())
mapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.NONE)
mapper.setVisibility(PropertyAccessor.FIELD, JsonAutoDetect.Visibility.ANY)
mapper.setVisibility(PropertyAccessor.CREATOR, JsonAutoDetect.Visibility.ANY)
val deserialized = mapper.readValue("1", JacksonTest::class.java) // throws exception
Assert.assertEquals(deserialized, JacksonTest.TEST2)
}
@Test
fun jacksonTest1_defaultMapper_succeeds() {
val mapper = ObjectMapper()
mapper.registerModule(KotlinModule())
val deserialized = mapper.readValue("1", JacksonTest::class.java)
Assert.assertEquals(deserialized, JacksonTest.TEST2)
}
@Test
fun jacksonTest2_customMapper_succeeds() {
val mapper = ObjectMapper()
mapper.registerModule(KotlinModule())
mapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.NONE)
mapper.setVisibility(PropertyAccessor.FIELD, JsonAutoDetect.Visibility.ANY) // works with or without this
mapper.setVisibility(PropertyAccessor.CREATOR, JsonAutoDetect.Visibility.ANY) // works with or without this
val deserialized = mapper.readValue("1", JacksonTest2::class.java)
Assert.assertEquals(deserialized, JacksonTest2.TEST2)
}
@Test
fun jacksonTest2_defaultMapper_succeeds() {
val mapper = ObjectMapper()
mapper.registerModule(KotlinModule())
val deserialized = mapper.readValue("1", JacksonTest2::class.java)
Assert.assertEquals(deserialized, JacksonTest2.TEST2)
}
It seems that in the first case, when both parameters have the same name and ObjectMapper uses customized visibility, then Jackson tries to call that @JsonCreator
method with a String parameter instead of an Int.
com.fasterxml.jackson.databind.JsonMappingException: Can not construct instance of ***.model.JacksonTest, problem: argument type mismatch at [Source: 1; line: 1, column: 1]
But for some reason, if the parameters have different names, then everything is working as expected.