Jackson-databind: Deserializing enum by using String argument @JsonCreator method fails in 2.11.0

Created on 27 May 2020  路  8Comments  路  Source: FasterXML/jackson-databind

I upgraded Jackson version from 2.10.1 to 2.11.0.
After upgrading, deserializing enum by using a delegating method that is annotated @JsonCreator and has a single String argument, fails.

This is a sample code.
In Jackson.version = 2.10.1, it will complete successfully.
However, in Jackson.version = 2.11.0, it will fail by the following exception.

Exception in thread "main" com.fasterxml.jackson.databind.exc.ValueInstantiationException: Cannot construct instance of `sample.EnumDeserializeTest$FooEnum`, problem: argument type mismatch
 at [Source: (String)"1"; line: 1, column: 1]

package sample;

import java.io.IOException;

import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonValue;
import com.fasterxml.jackson.databind.ObjectMapper;

public class EnumDeserializeTest {

    public static void main(String[] args) throws IOException {
        ObjectMapper mapper = new ObjectMapper();
        String json = mapper.writeValueAsString(FooEnum.FOO);
        mapper.readValue(json, FooEnum.class);
    }

    enum FooEnum {
        FOO(1);

        private final Integer code;

        FooEnum(Integer code) {
            this.code = code;
        }

        @JsonValue
        public Integer getCode() {
            return code;
        }

        @JsonCreator
        static FooEnum of(String code) {
            return FOO;
        }
    }
}

Is this behavior what you expect?

2.11

All 8 comments

Thank you for reporting this issue. One minor request: could you include code that actually triggers the problem? I assume it is simple readValue(), but I want to make sure I know exact JSON. If it's JSON String value, yes, this seems like a bug. And I'd like to reproduce and fix it.

Sure, but code that actually triggers the problem has business logic. because of this, I submit other sample json and code.

package sample;

import java.io.IOException;

import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonValue;
import com.fasterxml.jackson.databind.ObjectMapper;

public class EnumDeserializeTest {

    public static void main(String[] args) throws IOException {
        ObjectMapper mapper = new ObjectMapper();
        mapper.readValue("{\"id\":1001, \"fooType\":1}", SampleModel.class);
    }

    static class SampleModel {
        public Long id;
        public FooType fooType;
    }

    enum FooType {
        FOO(1);

        private final Integer code;

        FooType(Integer code) {
            this.code = code;
        }

        @JsonValue
        public Integer getCode() {
            return code;
        }

        @JsonCreator
        static FooType of(String code) {
            return FOO;
        }
    }
}

@ta7uw Thank you, that is perfectly fine -- all I need is a reproduction, not necessarily full code you had when originally encountering the problem.

@ta7uw I think I can see the problem: your JSON has number 1, but of method expects String value -- this is a mismatch. I am not sure how things appeared to be working before, but to work shouldn't you rather have:

        @JsonCreator
        static FooType of(int code) {
            return FOO;
        }

I do think that exception message in this case is not clear on specific mismatch, bit it does make sense to me.

Or, if you do not care about incoming value, have nominal type be Object (which matches any incoming JSON value)?

It seems that checking whether a type matches is added in 2.11.0. this causes this behavior.
https://github.com/FasterXML/jackson-databind/blob/master/src/main/java/com/fasterxml/jackson/databind/deser/std/FactoryBasedEnumDeserializer.java#L126-L134

It does make sense to me.
Thank you for your support!

@ta7uw It is unfortunate that code earlier somehow did work in this case -- I am not quite sure why this is, I would have expected a runtime exception for mismatch. Oh, actually, I think I do know: code was assuming value is String, and using getText() or getValueAsText(), which in case of numeric value would have coerced quietly into String.
This behavior prevented use of factory method that takes int or long value; which is I think why code was changed. Not sure if that could have been #2605, but I now thing I remember this change.

I getting the same problem after migrating from 2.10.x to 2.11 (with spring-boot 2.2.7 to 2.3.1).

I have an enum with an int id, a String name and a String label (for i18n), and my JSON should use the String name when serializing/deserializing, the whole object on some case when showing the enumlist into the UI, and the id is used for the database.

To fix that I removed the @JsonCreator from my method used for the convertion from the name and moved back to a @JsonDeserialize class that I removed a long time ago. For me it's a regression !

@jgribonvald as I said, unfortunately the previous working was wrong -- it should not have worked that way. This sometimes happens, buggy behavior considered feature. It is also particularly difficult thing to test against as there is unlimited number of things not designed to work.

Was this page helpful?
0 / 5 - 0 ratings