Skip to content

enum @JsonCreator method called with wrong parameter type when using customized visibility ObjectMapperΒ #78

Open
@rnpy

Description

@rnpy

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.

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions