Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Jackson cannot deserialize enum as object even if I add customized deserializer

I want to use Jackson JSON to serialize/deserialize a class containing an enum object. My class is:

class Bar {

    @JsonProperty("rateType")
    @JsonDeserialize(using = ReturnedRateTypeDeserializer.class)
    private ReturnedRateType rateType;

    public ReturnedRateType getRateType() {
        return rateType;
    }

    public void setRateType(ReturnedRateType rateType) {
        this.rateType = rateType;
    }
}

The enum class ReturnedRateType is defined as:

@JsonFormat(shape = JsonFormat.Shape.OBJECT)
public enum ReturnedRateType {
    AA("AA"),
    BB("BB"),
    CC("CC");

    @JsonProperty("value")
    private String value;

    ReturnedRateType(String value) {
        this.value = value;
    }

    @JsonCreator
    public static ReturnedRateType fromValue(final String value) {
        if (value != null) {
            for (ReturnedRateType type : ReturnedRateType.values()) {
                if (value.equalsIgnoreCase(type.value)) {
                    return type;
                }
            }
        }
        return null;
    }
}

As you see, I added @JsonFormat annotation to tell Jackson to serialize this enum as POJO, and added @JsonCreator annotation to get a static factory method from given string to enum object. Since Jackson can only serialize but can't deserialize from object representation to enum, I added the following customized deserializer for the enum ReturnedRateType:

public class ReturnedRateTypeDeserializer extends JsonDeserializer<ReturnedRateType> {

    @Override
    public ReturnedRateType deserialize(JsonParser jp, DeserializationContext ctxt) throws IOException, JsonProcessingException {
        ReturnedRateType type = ReturnedRateType.fromValue(jp.getValueAsString());
        if(type != null)
            return type;
        throw new JsonMappingException("invalid value for ReturnedRateType");
    }
} 

But when I tested deserialization from a JSON string to enum, I got the error. The JSON string is:

{"rateType": {"value": "AA"}}

My test code is:

@Test
public void RateTypeToEnum() {
    String json = "{\"rateType\": {\"value\": \"AA\"}}";
    System.out.println(json);
    ObjectMapper mapper = new ObjectMapper();
    Bar bar = null;
    try {
        bar = mapper.readValue(json, Bar.class);
    } catch (IOException e) {
        // TODO Auto-generated catch block
        e.printStackTrace();
    }
    System.out.println(bar.getRateType());
}

I expect to see the output should be AA. But jp.getValueAsString() in my customized deserializer ReturnedRateTypeDeserializer is null during the execution:

ReturnedRateType type = ReturnedRateType.fromValue(jp.getValueAsString());  //jp.getValueAsString() is null here!

Thus it returns error. So what is wrong here?

like image 483
tonga Avatar asked Apr 10 '15 16:04

tonga


1 Answers

According to the Jackson 2.5.X documentation on the JsonFormat annotation the Shape.Object does not work for the enum deserialisation:

  • Enums: Shapes JsonFormat.Shape.STRING and JsonFormat.Shape.NUMBER can be used to change between numeric (index) and textual (name or toString()); but it is also possible to use JsonFormat.Shape.OBJECT to serialize (but not deserialize).

I'd make the JsonCreator static method accept a JsonNode and read the string value from it.

Note that this would work since 2.5.X. In early versions you would need to write a custom deserialiser. Here is an example:

public class JacksonEnumObjectShape {
    @JsonFormat(shape = JsonFormat.Shape.OBJECT)
    @JsonDeserialize(using = ReturnedRateTypeDeserializer.class)
    public enum ReturnedRateType {
        AA("AA"),
        BB("BB"),
        CC("CC");

        @JsonProperty("value")
        private String value;

        ReturnedRateType(String value) {
            this.value = value;
        }

        @JsonCreator
        public static ReturnedRateType fromValue(final JsonNode jsonNode) {

            for (ReturnedRateType type : ReturnedRateType.values()) {
                if (type.value.equals(jsonNode.get("value").asText())) {
                    return type;
                }
            }
            return null;
        }
    }
    // can be avoided since 2.5
    public static class ReturnedRateTypeDeserializer extends JsonDeserializer<ReturnedRateType> {

        @Override
        public ReturnedRateType deserialize(
                final JsonParser jp,
                final DeserializationContext ctxt) throws IOException {
            final JsonNode jsonNode = jp.readValueAsTree();
            return ReturnedRateType.fromValue(jsonNode);
        }
    }

    public static void main(String[] args) throws IOException {
        final ObjectMapper mapper = new ObjectMapper();
        final String json = mapper.writeValueAsString(ReturnedRateType.AA);
        System.out.println(json);
        System.out.println(mapper.readValue(json, ReturnedRateType.class));
    }
}

Output:

{"value":"AA"}
AA
like image 63
Alexey Gavrilov Avatar answered Sep 24 '22 19:09

Alexey Gavrilov