Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Deserializing enum Shape.OBJECT using Jackson fails

I have the following enum declaration:

@Document
@JsonFormat(shape= JsonFormat.Shape.OBJECT)
@JsonAutoDetect()
public enum Compass {
    north("Upper Center"),
    south("Lower Center"),
    east("Left Center"),
    west("Right Center"),
    ne("Upper Right"),
    nw("Upper Left"),
    se("Lower Right"),
    sw("Lower Left"),
    ;

    @JsonProperty   
    private String presentableName;
    @JsonProperty   
    private String name;

    private Compass() {}
    private Compass(String presentableName) {
        this.presentableName = presentableName;
    }

    public String getPresentableName() {
        return presentableName;
    }
    public void setPresentableName(String presentableName) {
        this.presentableName = presentableName;
    }
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }

    @JsonCreator
    public static Compass fromObject(@JsonProperty("name") String name, @JsonProperty("presentableName") String presentableName) { 
        return Compass.sw;
    }
}

The input arrives as a json object and most of it is deserialized correctly, but the relevant part is as below, where placement is a Compass:

{"placement":{"name":"se","presentableName":"Lower Right"}}

Deserialization doesn't work. I thought a JsonCreator would work here, but for some reason I'm getting a

org.springframework.web.HttpMediaTypeNotSupportedException: Content type 'application/json;charset=UTF-8' not supported

which is actually just a symptom of deserialization failure.

If I change the creator to:

@JsonCreator
public static Compass fromObject(@JsonProperty("name") String name) { 
    return Compass.sw;
}

It gets even more wierd, because now name equals { instead of se (which looks like a bug in the json object, but it's the same object that got deserialized a second ago so it's probably ok)

I'm using jackson 2.2.3 which is the latest right now.

like image 300
TheZuck Avatar asked May 27 '14 18:05

TheZuck


People also ask

How does Jackson deserialize Enum?

By default, Jackson will use the Enum name to deserialize from JSON. If we want Jackson to case-insensitively deserialize from JSON by the Enum name, we need to customize the ObjectMapper to enable the ACCEPT_CASE_INSENSITIVE_ENUMS feature.

How do you serialize and deserialize an Enum with Jackson?

In order to serialize Enum, we take the help of ObjectMapper class. We use the writeValueAsString() method of ObjectMapper class for serializing Enum. If we serialize Enum by using the writeValueAsString() method, it will represent Java Enums as a simple string.

How to serialize Enum in Java?

You can serialize the enum by converting the enum to its ordinal() value.


1 Answers

Whilst I still agree with @renatoaraujoc's answer, I've found a (somehow hacky) solution to this problem.

Just create a @JsonCreator annotated method which receives a Map<String, Object>. Jackson deserializes the compound object into a Map as passes it to the method:

@JsonFormat(shape = JsonFormat.Shape.OBJECT)
public enum TesterEnum {
    FirstValue(1, "One"),
    SecondValue(2, "Two");

    private int id;
    private String dsc;

    TesterEnum(int id, String dsc) {
        this.id = id;
        this.dsc = dsc;
    }

    public int getId() {
        return id;
    }
    public void setId(int id) {
        this.id = id;
    }
    public String getDsc(){
        return dsc;
    }
    public void setDsc(String dsc){
        this.dsc = dsc;
    }

    @JsonCreator
    public static TesterEnum fromObject(final Map<String, Object> obj) {
        if (obj != null && obj.containsKey("id")) {
            Integer id = null;
            if (obj.get("id") instanceof Integer) {
                id = (Integer)obj.get("id");
            } else {
                id = Integer.parseInt((String)obj.get("id"));
            }
            return fromId(id);
        }
        return null;
    }
    public static TesterEnum fromId(final Integer id) {
        if (id != null) {
            for (TesterEnum e : TesterEnum.values()) {
                if (id.equals(e.getId())) return e;
            }
        }
        return null;
    }
}

P.S.: You don't really need the fromId method, it can be all put into fromObject, but I use the first on other parts of the system and like to have this separation.

P.S.²: Jackson normally decode the id field into an Integer instance, so it enters the if (obj.get("id") instanceof Integer) on fromObject, but I've seen it being cast to String sometimes, that's why I'm checking.

like image 96
Dinei Avatar answered Sep 28 '22 03:09

Dinei