Jackson: Serialize and deserialize enum values as integers
Asked Answered
L

8

53

Consider the following enum and class:

public enum State {
    OFF,
    ON,
    UNKNOWN
}

public class Machine {
    String name;
    int numCores;
    State state;

    public Machine(String name, int numCores, State state) {
        this.name = name;
        this.numCores = numCores;
        this.state = state;
    }
}

And consider the following main function:

public static void main(String args[]) {
    Machine m = new Machine("Machine 1", 8, State.OFF);
    ObjectMapper mapper = new ObjectMapper();
    String machineAsJsonString = mapper.writeValueAsString(m);
    System.out.println(machineAsJsonString);
}

Currently, the output of this main is:

{"name" : "Machine 1", "numCores" : 8, "state" : "OFF"}

This output is not good for me, as instead of the string "OFF" for state, I would like it to be 0, which is the ordinal value of OFF in the enum State.

So the actual result I want to get is:

{"name" : "Machine 1", "numCores" : 8, "state" : 0}

Is there some elegant way to make it behave this way?

Landre answered 15/6, 2016 at 11:1 Comment(3)
My jab at an annotation free implementation. Do reply. pastebin.com/raw/Mvf9Ygq1Note
ON = 0, OFF = 1, does NOT look like a friendly thing IMHO ... just saying ;-)Adventist
@Adventist it took me 4 years, but I've finally taken your advice and fixed it :PLandre
M
92

It should work by specifying JsonValue mapper.

public enum State {
    OFF,
    ON,
    UNKNOWN;

    @JsonValue
    public int toValue() {
        return ordinal();
    }
}  

This works for deserialization also, as noted in Javadoc of @JsonValue annotation:

NOTE: when use for Java enums, one additional feature is that value returned by annotated method is also considered to be the value to deserialize from, not just JSON String to serialize as. This is possible since set of Enum values is constant and it is possible to define mapping, but can not be done in general for POJO types; as such, this is not used for POJO deserialization

Mutilate answered 15/6, 2016 at 11:16 Comment(3)
In fact, if you put a debug statement on toValue() method above, you'll notice that Jackson makes as many calls to toValue() as many ENUM values are.Aspa
This solution works only if we are dealing with enum ordinals. If enums have an integer field, @JsonValue fails to work and we need to declare a static method with @JsonCreator annotation to resolve the appropriate enum. Here is an open bug for the same - github.com/FasterXML/jackson-databind/issues/1850Transversal
Also it's possible to use Mixin for Enum, e.g. interface EnumMixin { @JsonValue int ordinal(); } - do not forget to register it in object mapper.Eradicate
T
27

You can use setting

objectMapper.enable(SerializationFeature.WRITE_ENUMS_USING_INDEX);

See https://github.com/FasterXML/jackson-databind/blob/master/src/test/java/com/fasterxml/jackson/databind/ser/TestEnumSerialization.java for complete test cases

Thanks to tip at https://righele.it/2016/01/30/jackson-deserialization-from-json-to-java-enums/

Throstle answered 4/5, 2017 at 11:18 Comment(3)
Excellent. This should be the accepted answer, less intrusiveRonnironnica
It is more intrusive as affects ALL enums serialized by that ObjectMapper, so you lose ability to control it yourself...Algeciras
do I need @JsonSerialize at enum file?Adan
A
16

You can use in this way

import com.fasterxml.jackson.annotation.JsonFormat;

@JsonFormat(shape = JsonFormat.Shape.NUMBER)
public enum State {
       OFF,
       ON,
       UNKNOWN
}
Arndt answered 24/5, 2019 at 3:30 Comment(0)
W
6

Yet another way:

public enum State {

    @JsonProperty("0")
    OFF,

    @JsonProperty("1")
    ON,

    @JsonProperty("2")
    UNKNOWN
}

However, this will produce {"state" : "1"} instead of {"state" : 1} (string, not numeric). In most cases it's OK

Wards answered 6/9, 2018 at 10:4 Comment(3)
It may work, but this also requires you to write @JsonProperty before each enum item and maintain the ordinal numbers by yourself. Implementing a method that just returns the ordinal, turns to be a much better practice and much more robust.Landre
@Landre from the other hand placing a new enum value between, say, OFF and UNKNOWN (or deleting existing one) will break the ordinal-based behavior, so maintenance will be required anywayWards
That's right. Agreed that your answer is worth being postedLandre
H
5

For completion I post another way: custom serializer:

public class StateSerializer extends JsonSerializer<State> {  
    public void serialize(State value, JsonGenerator generator, SerializerProvider provider) throws IOException, JsonProcessingException {
        generator.writeStartObject();
        generator.writeFieldName("id");
        generator.writeNumber(value.getId());
        generator.writeEndObject();
    }
}

@JsonSerialize(using = StateSerializer.class)
public enum State { 
    ...
    public int getId(){...}
}
Hinds answered 15/6, 2016 at 11:10 Comment(0)
H
2

If you want to print the ordinal of the enum you can change your constructor to accept an int instead of State and then in your call to Machine you can structure it in the following way:

Machine m = new Machine("Machine 1", 8, State.OFF.ordinal());

This will get the enum ordinal value of the passed in state and print the following

{name='Machine 1', numCores=8, state=0}

Hueyhuff answered 15/6, 2016 at 11:8 Comment(3)
I like this workaround. I'll wait for other answers, as there might be more elegant ways to do itLandre
@TomC your first line of code will prone to a compile time error because of argument (last) is integer in actual argument while Constructor having enum type actually .. Thank yoKirstiekirstin
@VikrantKashyap I specifically stated in my answer that that the constructor would have to be changed from a type of State to int See: change your constructor to accept an int instead of StateHueyhuff
B
0

For integer enums, in kotlin, this works well. In java as well. If you don't want to use ordinals.

enum class State(@get:JsonValue val state: Int) {
          OFF(0),
          ON(1),
          UNKNOWN(-1)
}

Byte code generated is:

@JsonValue
public final int getState() {
  return this.state;

}

Burma answered 18/7, 2022 at 16:46 Comment(0)
C
0

I had autogenerated enum classes, and could not add @JsonValue (or @XmlValue). I resolved this with custom serialize method, where I added:

objectMapper.enable(SerializationFeature.WRITE_ENUMS_USING_TO_STRING);

Complete method was like this, for example:

private static ObjectMapper createXmlMapper() {
  ObjectMapper objectMapper = new XmlMapper();
  objectMapper.registerModule(new JaxbAnnotationModule());
  JacksonXmlModule xmlModule = new JacksonXmlModule();
  xmlModule.setDefaultUseWrapper(false);
  objectMapper.registerModule(xmlModule);
  objectMapper.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS);
  objectMapper.enable(SerializationFeature.WRITE_ENUMS_USING_TO_STRING);  // to overcome jackson enum serialization issue, where enum name is returned instead of value
  objectMapper.registerModule(new JavaTimeModule());
  return objectMapper;
}

// Method to serialize object to XML using XmlMapper
public static String serializeToXml(Object object) throws JsonProcessingException {
  ObjectMapper objectMapper = createXmlMapper();
  return objectMapper.writeValueAsString(object);
}
Conoscenti answered 19/9, 2023 at 12:35 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.