Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Deserialize java enum from JSON

We use Jackson 1.9.1 to serialize and deserialize JSON request response strings to/from Java objects. Primitive Java types, collection types, and custom objects are (de)serialized without issues. However, I have a problem trying to deserialize JSON string into java enum. JSON string is serialized like so:

"wt":{"wt":100.5,"unit":{"LBS":3}}

Java type for wt is like so:

public class Weight {

    protected double weight;
    protected Unit unit;
}

I referred to this, this, and this on SO and came up with enum for weight units like so:

public enum Unit {

    KG("kg"),
    GM("gm"),
    LBS("lbs"),
    OZ("oz");

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

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

    @JsonCreator
    public static Unit create(String val) {
        Unit[] units = Unit.values();
        for (Unit unit : units) {
            if (unit.getValue().equals(val)) {
                return unit;
            }
        }
        return LBS;
    }
}

The problem is, when ever I try to deserialize above mentioned JSON I get this error saying: "Unrecognized field "LBS" (Class a.b.c.d.Weight), not marked as ignorable" Exception stacktrace is like so:

Caused by: org.codehaus.jackson.map.exc.UnrecognizedPropertyException: Unrecognized field "LBS" (Class a.b.c.d.Weight), not marked as ignorable
 at [Source: java.io.ByteArrayInputStream@20172017; line: 1, column: 464] (through reference chain: a.b.c.d.MyRequest["blah"]->a.b.c.d.AnotherType["wt"]->a.b.c.d.Weight["LBS"])
    at org.codehaus.jackson.map.exc.UnrecognizedPropertyException.from(UnrecognizedPropertyException.java:53)
    at org.codehaus.jackson.map.deser.StdDeserializationContext.unknownFieldException(StdDeserializationContext.java:267)
    at org.codehaus.jackson.map.deser.std.StdDeserializer.reportUnknownProperty(StdDeserializer.java:673)
    at org.codehaus.jackson.map.deser.std.StdDeserializer.handleUnknownProperty(StdDeserializer.java:659)
    at org.codehaus.jackson.map.deser.BeanDeserializer.handleUnknownProperty(BeanDeserializer.java:1365)

...

My questions are: Is the serialized JSON string for enum seem correct ? What else should I include (or annotate) for the enum to be properly deserialized ?

like image 581
chetan Avatar asked Aug 29 '13 07:08

chetan


People also ask

How does Jackson deserialize enum?

All the methods annotated by @JsonCreator are invoked by Jackson for getting an instance of the enclosing class. In order to deserialize the JSON String to Enum by using the @JsonCreator, we will define the forValues() factory method with the @JsonCreator annotation. We have the following JSON string to deserialize: {

How do you serialize an enum?

To serialize an enum constant, ObjectOutputStream writes the value returned by the enum constant's name method. To deserialize an enum constant, ObjectInputStream reads the constant name from the stream; the deserialized constant is then obtained by calling the java.

Does JSON support enum?

JSON has no enum type. The two ways of modeling an enum would be: An array, as you have currently. The array values are the elements, and the element identifiers would be represented by the array indexes of the values.

Can enum implement serializable?

Because enums are automatically Serializable (see Javadoc API documentation for Enum), there is no need to explicitly add the "implements Serializable" clause following the enum declaration.


2 Answers

I am assuming that in the public enum Unit code block, you mean Unit instead of WeightMeasurementUnit.

The Weight class has only a weight and a unit, so if you pass {"wt":100.5,"unit":"lbs"}, it should work, because a unit is just a unit without value. So there is no way for the deserializer to parse {"LBS":3} as a Unit. What is the 3 for?

Another problem is that your value is "lbs" whereas you are passing "LBS". So either you need to standardise or you need to use unit.getValue().equalsIgnoreCase(val)

like image 166
Hari Menon Avatar answered Sep 29 '22 22:09

Hari Menon


I would suggest you update your jackson version to 2.7.0-rc2 (and probably also before) and then configure the ObjectMapper as follows:

private ObjectMapper createObjectMapper() {
    final ObjectMapper mapper = new ObjectMapper();
    // enable toString method of enums to return the value to be mapped
    mapper.enable(SerializationFeature.WRITE_ENUMS_USING_TO_STRING);
    mapper.enable(DeserializationFeature.READ_ENUMS_USING_TO_STRING);
    return mapper;
}

In your enum you just have to override the toString() method:

public enum Unit {
    KG,
    GM,
    LBS,
    OZ;

    // UPDATE: implicitly already the default so override not needed in this case
    @Override
    public String toString() {
        return name();
    }
}

You don't need any annotations or custom deserializers. This would be the way to map a simple enum to a json and vice-versa.

If your enum should be mapped from a special string you have to add a value field and a Constructor which assigns this field and return the value in the toString method.

public enum Unit {
    KG("kilogram"),
    GM("gram"),
    LBS("blah"),
    OZ("anything");

    Unit(final String value) {
        this.value = value;
    }

    @Override
    public String toString() {
        return value;
    }
}
like image 36
luckyhandler Avatar answered Sep 29 '22 20:09

luckyhandler