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

New values make Enums backward incompatible #728

Closed
avadhoot opened this issue May 18, 2017 · 3 comments
Closed

New values make Enums backward incompatible #728

avadhoot opened this issue May 18, 2017 · 3 comments

Comments

@avadhoot
Copy link

avadhoot commented May 18, 2017

Problem: If I add a new value to an existing Enum and start returning it from my service any client who has not upgraded to the latest version of the JAR containing the new Enum will start getting exceptions during deserialization even if the DeserializationFeature.READ_UNKNOWN_ENUM_VALUES_AS_NULL has been set to enabled.

Here is a sample Enum being returned from the service:

public enum MyEnum {

    VALUE1("VALUE1"),
    VALUE2("VALUE2"),
    VALUE3("VALUE3");

    private final String value;
    private static Map<String, MyEnum> constants = new HashMap();

    private MyEnum(String value) {
        this.value = value;
    }

    @JsonValue
    public String toString() {
        return this.value;
    }

    @JsonCreator
    public static MyEnum fromValue(String value) {
        MyEnum constant = (MyEnum)constants.get(value);
        if(constant == null) {
            throw new IllegalArgumentException(value);
        } else {
            return constant;
        }
    }

    static {
        MyEnum[] arr$ = values();
        int len$ = arr$.length;

        for(int i$ = 0; i$ < len$; ++i$) {
            MyEnum c = arr$[i$];
            constants.put(c.value, c);
        }
    }
}

This is the Enum on the client side:

public enum MyEnum {

    VALUE1("VALUE1"),
    VALUE2("VALUE2");

    private final String value;
    private static Map<String, MyEnum> constants = new HashMap();

    private MyEnum(String value) {
        this.value = value;
    }

    @JsonValue
    public String toString() {
        return this.value;
    }

    @JsonCreator
    public static MyEnum fromValue(String value) {
        MyEnum constant = (MyEnum)constants.get(value);
        if(constant == null) {
            throw new IllegalArgumentException(value);
        } else {
            return constant;
        }
    }

    static {
        MyEnum[] arr$ = values();
        int len$ = arr$.length;

        for(int i$ = 0; i$ < len$; ++i$) {
            MyEnum c = arr$[i$];
            constants.put(c.value, c);
        }
    }
}

Now if the service returns 'VALUE3' then the client's deserialization will fail even if DeserializationFeature.READ_UNKNOWN_ENUM_VALUES_AS_NULL is enabled.
The reason for this is that the ObjectMapper's deserialization method will always call fromValue as it is annotated with @JsonCreator. If this annotation is removed the toString method gets called which won't throw the exception but return NULL which can then be handled by the client.

My requirement is for the Enums to be backward compatible to support clients which integrate with older versions of my service response.

What is the best way to handle this problem?

@joelittlejohn
Copy link
Owner

@avadhoot When you introduce a new value into an enum that was not present previously, you have made a breaking change to the API, since the new data will no longer be valid against the previous schema. Clients will not expect to receive this value so this is a breaking change.

I can see that DeserializationFeature.READ_UNKNOWN_ENUM_VALUES_AS_NULL is a useful one to create more relaxed clients though.

So if I understand this correctly, READ_UNKNOWN_ENUM_VALUES_AS_NULL is ignored by the Jackson enum deserializer if there is a method marked with @JsonCreator, is that right? I think maybe this is a bug/feature request for Jackson not jsonschema2pojo. If DeserializationFeature.READ_UNKNOWN_ENUM_VALUES_AS_NULL is true, then IllegalArgumentException from the @JsonCreator method should be treated as null. Does this make sense?

We could always implement our own config flag here to allow unknown enum values, so we would replace this code:

        MyEnum constant = (MyEnum)constants.get(value);
        if(constant == null) {
            throw new IllegalArgumentException(value);
        } else {
            return constant;
        }

with this code:

        return (MyEnum)constants.get(value);

However I think this is really a Jackson issue.

@joelittlejohn
Copy link
Owner

I've raised a PR against jackson-databind:

FasterXML/jackson-databind#1642

@avadhoot
Copy link
Author

avadhoot commented Aug 7, 2017

Thanks a lot for taking the time to fix this issue.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants