Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Deserializing enum error by using int/Integer value with @JsonValue #2754

Closed
zzzzbw opened this issue Jun 9, 2020 · 5 comments
Closed

Deserializing enum error by using int/Integer value with @JsonValue #2754

zzzzbw opened this issue Jun 9, 2020 · 5 comments
Labels
duplicate Duplicate of an existing (usually earlier) issue

Comments

@zzzzbw
Copy link

zzzzbw commented Jun 9, 2020

When annotating @JsonValue on the int/Integer value, the enum can be serialized as number successfully. when i deserializing it by using a number JSON string, it will throw exception.

But it work well when using a 'String' type JSON string.

Whether the type of serialized values and deserialized parameters should be consistent?

follow is some sample code:

public class Test {
    public static void main(String[] args) {
        final ObjectMapper mapper = new ObjectMapper();

        try {
            // work well as "10" JSON string
            ColorEnum colorEnum1 = mapper.readValue("\"10\"", ColorEnum.class);
            System.out.println(colorEnum1);
        } catch (JsonProcessingException e) {
            e.printStackTrace();
        }
        try {
            // fail as 10 JSON string
            ColorEnfum colorEnum2 = mapper.readValue("10", ColorEnum.class);
            System.out.println(colorEnum2);
        } catch (JsonProcessingException e) {
            e.printStackTrace();
        }

        try {
            // fail when using serialized generated string
            String json = mapper.writeValueAsString(ColorEnum.RED);
            ColorEnum colorEnum3 = mapper.readValue(json, ColorEnum.class);
            System.out.println(colorEnum3);
        } catch (JsonProcessingException e) {
            e.printStackTrace();
        }
    }

    enum ColorEnum {
        RED(10),
        YELLOW(20),
        GREEN(30);

        ColorEnum(Integer value) {
            this.value = value;
        }

        @JsonValue
        private final Integer value;

        public Integer getValue() {
            return value;
        }
    }
}

the result is:

RED
com.fasterxml.jackson.databind.exc.InvalidFormatException: Cannot deserialize value of type `cn.zzzzbw.springboot.Test$ColorEnum` from number 10: index value outside legal index range [0..2]
 at [Source: (String)"10"; line: 1, column: 1]
	at com.fasterxml.jackson.databind.exc.InvalidFormatException.from(InvalidFormatException.java:67)
	at com.fasterxml.jackson.databind.DeserializationContext.weirdNumberException(DeserializationContext.java:1710)
	at com.fasterxml.jackson.databind.DeserializationContext.handleWeirdNumberValue(DeserializationContext.java:992)
	at com.fasterxml.jackson.databind.deser.std.EnumDeserializer.deserialize(EnumDeserializer.java:200)
	at com.fasterxml.jackson.databind.ObjectMapper._readMapAndClose(ObjectMapper.java:4482)
	at com.fasterxml.jackson.databind.ObjectMapper.readValue(ObjectMapper.java:3434)
	at com.fasterxml.jackson.databind.ObjectMapper.readValue(ObjectMapper.java:3402)
	at cn.zzzzbw.springboot.Test.main(Test.java:23)
com.fasterxml.jackson.databind.exc.InvalidFormatException: Cannot deserialize value of type `cn.zzzzbw.springboot.Test$ColorEnum` from number 10: index value outside legal index range [0..2]
 at [Source: (String)"10"; line: 1, column: 1]
	at com.fasterxml.jackson.databind.exc.InvalidFormatException.from(InvalidFormatException.java:67)
	at com.fasterxml.jackson.databind.DeserializationContext.weirdNumberException(DeserializationContext.java:1710)
	at com.fasterxml.jackson.databind.DeserializationContext.handleWeirdNumberValue(DeserializationContext.java:992)
	at com.fasterxml.jackson.databind.deser.std.EnumDeserializer.deserialize(EnumDeserializer.java:200)
	at com.fasterxml.jackson.databind.ObjectMapper._readMapAndClose(ObjectMapper.java:4482)
	at com.fasterxml.jackson.databind.ObjectMapper.readValue(ObjectMapper.java:3434)
	at com.fasterxml.jackson.databind.ObjectMapper.readValue(ObjectMapper.java:3402)
	at cn.zzzzbw.springboot.Test.main(Test.java:31)

Process finished with exit code 0

ps: the version is 2.11.0

@zzzzbw
Copy link
Author

zzzzbw commented Jun 9, 2020

I found the code that caused this situation

int the com.fasterxml.jackson.databind.deser.std.EnumDeserializer#deserialize(JsonParser p, DeserializationContext ctxt)

it will deserialized the enum using index directly if curr == JsonToken.VALUE_NUMBER_INT.

public Object deserialize(JsonParser p, DeserializationContext ctxt) throws IOException
    {
        JsonToken curr = p.currentToken();

        // Usually should just get string value:
        if (curr == JsonToken.VALUE_STRING || curr == JsonToken.FIELD_NAME) {
            CompactStringObjectMap lookup = ctxt.isEnabled(DeserializationFeature.READ_ENUMS_USING_TO_STRING)
                    ? _getToStringLookup(ctxt) : _lookupByName;
            final String name = p.getText();
            Object result = lookup.find(name);
            if (result == null) {
                return _deserializeAltString(p, ctxt, lookup, name);
            }
            return result;
        }
        // But let's consider int acceptable as well (if within ordinal range)
        if (curr == JsonToken.VALUE_NUMBER_INT) {
            // ... unless told not to do that
            int index = p.getIntValue();
            if (ctxt.isEnabled(DeserializationFeature.FAIL_ON_NUMBERS_FOR_ENUMS)) {
                return ctxt.handleWeirdNumberValue(_enumClass(), index,
                        "not allowed to deserialize Enum value out of number: disable DeserializationConfig.DeserializationFeature.FAIL_ON_NUMBERS_FOR_ENUMS to allow"
                        );
            }
            if (index >= 0 && index < _enumsByIndex.length) {
                return _enumsByIndex[index];
            }
            if ((_enumDefaultValue != null)
                    && ctxt.isEnabled(DeserializationFeature.READ_UNKNOWN_ENUM_VALUES_USING_DEFAULT_VALUE)) {
                return _enumDefaultValue;
            }
            if (!ctxt.isEnabled(DeserializationFeature.READ_UNKNOWN_ENUM_VALUES_AS_NULL)) {
                return ctxt.handleWeirdNumberValue(_enumClass(), index,
                        "index value outside legal index range [0..%s]",
                        _enumsByIndex.length-1);
            }
            return null;
        }
        return _deserializeOther(p, ctxt);
    }

It might be a bug? When the int/Integer parameter is annotated by @JsonValue , whether consider using annotations instead of index?

@cowtowncoder
Copy link
Member

cowtowncoder commented Jun 9, 2020

I think this is because currently @JsonValue with Enums only supports Strings. It would be good to allow use you are showing here, as it does make sense, but current deserializer only supports int value used as an index.

For now, workaround would be to create static factory method annotated with @JsonCreator, something like:

@JsonCreator(mode = JsonCreator.Mode.DELEGATING)
static ColorEnum find(int value) {
    // find matching isntance
}

which is more work but actually works.

We can keep this issue open as request for enhancement.

@zzzzbw
Copy link
Author

zzzzbw commented Jun 10, 2020

This really works using static factory method with @JsonCreator.

But threre are a lot of enum like this in my project. The factory method is static mean can‘t be inherit so that i have to write manually for every enum.

Thanks very much if can solve this problem in the next version

@cowtowncoder
Copy link
Member

@zzzzbw I agree, factory method is more work. It should be possible to add helper methods for building Map<Integer, Enum>, simplifying implementation to just couple of copy-paste lines -- esp. since Enums can actually implement an interface (so generic code can use "get the integer id of this enum").

But I do hope we can support this in future as this is perfectly sensible usage pattern.

@cowtowncoder cowtowncoder added 2.13 and removed 2.12 labels Oct 27, 2020
@cowtowncoder cowtowncoder added duplicate Duplicate of an existing (usually earlier) issue and removed 2.13 labels Feb 20, 2021
@cowtowncoder
Copy link
Member

Realized that this is a duplicate of #1850 so closing in favor of that one.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
duplicate Duplicate of an existing (usually earlier) issue
Projects
None yet
Development

No branches or pull requests

2 participants