Jackson enum Serializing and DeSerializer
Asked Answered
P

19

290

I'm using JAVA 1.6 and Jackson 1.9.9 I've got an enum

public enum Event {
    FORGOT_PASSWORD("forgot password");

    private final String value;

    private Event(final String description) {
        this.value = description;
    }

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

I've added a @JsonValue, this seems to do the job it serializes the object into:

{"event":"forgot password"}

but when I try to deserialize I get a

Caused by: org.codehaus.jackson.map.JsonMappingException: Can not construct instance of com.globalrelay.gas.appsjson.authportal.Event from String value 'forgot password': value not one of declared Enum instance names

What am I missing here?

Pedigree answered 18/9, 2012 at 0:12 Comment(3)
Have you tried {"Event":"FORGOT_PASSWORD"}? Note the caps on both Event and FORGOT_PASSWORD.Midst
Similar: How to annotate enum fields for deserialization using Jackson jsonTracytrade
Who came here also check getter setter syntax if you follow different naming convention i.e. instead of getValue this GetValue doesn't workWeeping
B
345

The serializer / deserializer solution pointed out by @xbakesx is an excellent one if you wish to completely decouple your enum class from its JSON representation.

Alternatively, if you prefer a self-contained solution, an implementation based on @JsonCreator and @JsonValue annotations would be more convenient.

So leveraging on the example by @Stanley the following is a complete self-contained solution (Java 6, Jackson 1.9):

public enum DeviceScheduleFormat {

    Weekday,
    EvenOdd,
    Interval;

    private static Map<String, DeviceScheduleFormat> namesMap = new HashMap<String, DeviceScheduleFormat>(3);

    static {
        namesMap.put("weekday", Weekday);
        namesMap.put("even-odd", EvenOdd);
        namesMap.put("interval", Interval);
    }

    @JsonCreator
    public static DeviceScheduleFormat forValue(String value) {
        return namesMap.get(StringUtils.lowerCase(value));
    }

    @JsonValue
    public String toValue() {
        for (Entry<String, DeviceScheduleFormat> entry : namesMap.entrySet()) {
            if (entry.getValue() == this)
                return entry.getKey();
        }

        return null; // or fail
    }
}
Bipartisan answered 6/12, 2013 at 10:23 Comment(13)
@Agusti please take a look at my question,what am i missing there stackoverflow.com/questions/30525986/enum-is-not-bindingApheliotropic
maybe obvious to some, but note that @ JsonValue is used for serialization and @ JsonCreator for deserialization. If you're not doing both you'll only need one or the other.Javed
@Agusti can you please tell me how will I use(call) this enum in my jsonDeacon
I really dislike this solution for the simple fact that you introduce two sources of truth. The developer will always have to remember to add the name in two places. I much prefer a solution that just does the right thing without decorating the internals of an enum with a map.Organdy
@Organdy you can avoid that by adding the objects to the map during the constructorSymposium
@Symposium That doesn't mitigate the problem that someone needs to remember to add the name in two places. You're just suggesting moving the naming to a constructor instead of in a static initializer. Two sources of truth = not clean code.Organdy
@Organdy "someone needs to remember to add the name in two places", no you won't, it will be the same place, you can't declare it without specifying it, so you don't have to remember, the compiler will tell you to.Symposium
@Symposium You have to declare the elements in the enum (1st place) and then in the constructor or static initializer (2nd place). You have to record the actual name of the enumerated element, and then the pretty-printed name somewhere else. It's bad enough when they're right next to each other as in @JsonProperty but your solution means that they're not even guaranteed to be that close. I still don't like this solution.Organdy
@ttdbrd how about this for unifying truths? gist.github.com/Scuilion/036c53fd7fee2de89701a95822c0fb60Tenney
does not work for me, when I add JsonValue, Json creator starts being ignored.Varese
Instead of static map you can use YourEnum.values() which give Array of YourEnum and iterate on itIsraelite
Concerning the implication that @JsonValue is not intended for deserialization, I noticed the following statement in the JsonValue javadoc: "NOTE: when use for Java enums, one additional feature isthat value returned by annotated method is also considered to be thevalue to deserialize from, not just JSON String to serialize as.This is possible since set of Enum values is constant and it is possibleto define mapping, but can not be done in general for POJO types; as such,this is not used for POJO deserialization."Halfprice
An option is to add a private final String value field with getter and a static Map<String, MyEnum> map = Arrays.stream(values()).collect(Collectors.toMap(MyEnum::getValue,Function.identity()));Queeniequeenly
S
283

Note that as of this commit in June 2015 (Jackson 2.6.2 and above) you can now simply write:

public enum Event {
    @JsonProperty("forgot password")
    FORGOT_PASSWORD;
}

The behavior is documented here: https://fasterxml.github.io/jackson-annotations/javadoc/2.11/com/fasterxml/jackson/annotation/JsonProperty.html

Starting with Jackson 2.6 this annotation may also be used to change serialization of Enum like so:

 public enum MyEnum {
      @JsonProperty("theFirstValue") THE_FIRST_VALUE,
      @JsonProperty("another_value") ANOTHER_VALUE;
 }

as an alternative to using JsonValue annotation.

Shuttle answered 22/9, 2015 at 17:56 Comment(8)
nice solution. It's a shame I'm stuck with 2.6.0 bundled in Dropwizard :-(Ghassan
This will only serialize, not deserialize.Temporal
Unfortunately this does not return the property when converting your enum to a string.Sapid
This solution works for both serialize and deserialize on Enum. Tested in 2.8.Burress
Doesn't seem to be deprecated at all: github.com/FasterXML/jackson-annotations/blob/master/src/main/…Karimakarin
This didn't do anything for me, using Jackson 2.9.10.Halfprice
I added an official link to the (2.11) documentation, which explicitly states that @JsonProperty can be used like this in 2.6 and onwards.Shameful
worked for me in Jackson 2.11.2Castile
B
101

You should create a static factory method which takes single argument and annotate it with @JsonCreator (available since Jackson 1.2)

@JsonCreator
public static Event forValue(String value) { ... }

Read more about JsonCreator annotation here.

Brinkley answered 2/7, 2013 at 8:21 Comment(4)
This is the cleanest and most concise solution, the rest are just tons of boilerplate that could (and should!) be avoided at all costs!Ghassan
@JSONValue to serialize and @JSONCreator to deserialize.Kreis
@JsonCreator public static Event valueOf(int intValue) { ... } to deserialize int to Event enumerator.Backstitch
@ClintEastwood whether the other solutions should be avoided depends on whether you want to separate serialzation/ deserialization concerns from the enum or not.Nephridium
W
50

Actual Answer:

The default deserializer for enums uses .name() to deserialize, so it's not using the @JsonValue. So as @OldCurmudgeon pointed out, you'd need to pass in {"event": "FORGOT_PASSWORD"} to match the .name() value.

An other option (assuming you want the write and read json values to be the same)...

More Info:

There is (yet) another way to manage the serialization and deserialization process with Jackson. You can specify these annotations to use your own custom serializer and deserializer:

@JsonSerialize(using = MySerializer.class)
@JsonDeserialize(using = MyDeserializer.class)
public final class MyClass {
    ...
}

Then you have to write MySerializer and MyDeserializer which look like this:

MySerializer

public final class MySerializer extends JsonSerializer<MyClass>
{
    @Override
    public void serialize(final MyClass yourClassHere, final JsonGenerator gen, final SerializerProvider serializer) throws IOException, JsonProcessingException
    {
        // here you'd write data to the stream with gen.write...() methods
    }

}

MyDeserializer

public final class MyDeserializer extends org.codehaus.jackson.map.JsonDeserializer<MyClass>
{
    @Override
    public MyClass deserialize(final JsonParser parser, final DeserializationContext context) throws IOException, JsonProcessingException
    {
        // then you'd do something like parser.getInt() or whatever to pull data off the parser
        return null;
    }

}

Last little bit, particularly for doing this to an enum JsonEnum that serializes with the method getYourValue(), your serializer and deserializer might look like this:

public void serialize(final JsonEnum enumValue, final JsonGenerator gen, final SerializerProvider serializer) throws IOException, JsonProcessingException
{
    gen.writeString(enumValue.getYourValue());
}

public JsonEnum deserialize(final JsonParser parser, final DeserializationContext context) throws IOException, JsonProcessingException
{
    final String jsonValue = parser.getText();
    for (final JsonEnum enumValue : JsonEnum.values())
    {
        if (enumValue.getYourValue().equals(jsonValue))
        {
            return enumValue;
        }
    }
    return null;
}
Whoa answered 13/11, 2012 at 21:8 Comment(2)
The usage of custom (de)serializer kills the simplicity (which is using Jackson is worth for, btw), so this is needed in really heavy situations. Use @JsonCreator, as described below, and check this commentFlyte
This soluiton is best for the somewhat crazy problem introduced in the OPs question. The real issue here is that the OP wants to return the structured data in a rendered form. That is, they're returning data that already includes a user friendly string. But in order to turn the rendered form back into an identifier, we need some code that can reverse the transformation. The hacky accepted answer wants to use a map to handle the transformation, but requires more maintenance. With this solution, you can add new enumerated types and then your developers can get on with their jobs.Organdy
S
44

I've found a very nice and concise solution, especially useful when you cannot modify enum classes as it was in my case. Then you should provide a custom ObjectMapper with a certain feature enabled. Those features are available since Jackson 1.6. So you only need to write toString() method in your enum.

public class CustomObjectMapper extends ObjectMapper {
    @PostConstruct
    public void customConfiguration() {
        // Uses Enum.toString() for serialization of an Enum
        this.enable(WRITE_ENUMS_USING_TO_STRING);
        // Uses Enum.toString() for deserialization of an Enum
        this.enable(READ_ENUMS_USING_TO_STRING);
    }
}

There are more enum-related features available, see here:

https://github.com/FasterXML/jackson-databind/wiki/Serialization-Features https://github.com/FasterXML/jackson-databind/wiki/Deserialization-Features

Shanklin answered 2/7, 2014 at 17:37 Comment(5)
not sure why you need to extend the class. you can enable this feature on an instance of the ObjectMapper.Organdy
+1 because he pointed me to the [READ|WRITE]_ENUMS_USING_TO_STRING which I can use in Spring application.ymlRonaronal
Thanks, your answer helped me to resolve my issue in Retrofit If you want you use ordinal during serialization then use SerializationFeature.WRITE_ENUMS_USING_INDEX .Tote
thanks, for the config hits. It helped me to solve my issue.Assortment
This help me since the POJO's were external and have no way to update themTailstock
C
15

I like the accepted answer. However, I would improve it a little (considering that there is now Java higher than version 6 available).

Example:

    public enum Operation {
        EQUAL("eq"),
        NOT_EQUAL("ne"),
        LESS_THAN("lt"),
        GREATER_THAN("gt");

        private final String value;

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

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

        @JsonCreator
        public static Operation forValue(String value) {
            return Arrays.stream(Operation.values())
                .filter(op -> op.getValue().equals(value))
                .findFirst()
                .orElseThrow(); // depending on requirements: can be .orElse(null);
        }
    }
Crossquestion answered 23/3, 2021 at 16:8 Comment(0)
S
13

Try this.

public enum Event {

    FORGOT_PASSWORD("forgot password");

    private final String value;

    private Event(final String description) {
        this.value = description;
    }

    private Event() {
        this.value = this.name();
    }

    @JsonValue
    final String value() {
        return this.value;
    }
}
Sammons answered 4/4, 2019 at 21:29 Comment(0)
R
7

You can customize the deserialization for any attribute.

Declare your deserialize class using the annotationJsonDeserialize (import com.fasterxml.jackson.databind.annotation.JsonDeserialize) for the attribute that will be processed. If this is an Enum:

@JsonDeserialize(using = MyEnumDeserialize.class)
private MyEnum myEnum;

This way your class will be used to deserialize the attribute. This is a full example:

public class MyEnumDeserialize extends JsonDeserializer<MyEnum> {

    @Override
    public MyEnum deserialize(JsonParser jsonParser, DeserializationContext deserializationContext) throws IOException {
        JsonNode node = jsonParser.getCodec().readTree(jsonParser);
        MyEnum type = null;
        try{
            if(node.get("attr") != null){
                type = MyEnum.get(Long.parseLong(node.get("attr").asText()));
                if (type != null) {
                    return type;
                }
            }
        }catch(Exception e){
            type = null;
        }
        return type;
    }
}
Repulsive answered 8/10, 2015 at 21:32 Comment(2)
Nathaniel Ford, got better ?Repulsive
Yes, this is a much better answer; it provides some context. I would go even further, though, and discuss why adding deserialization in this manner addresses the OP's specific obstacle.Afrit
L
5

Here is another example that uses string values instead of a map.

public enum Operator {
    EQUAL(new String[]{"=","==","==="}),
    NOT_EQUAL(new String[]{"!=","<>"}),
    LESS_THAN(new String[]{"<"}),
    LESS_THAN_EQUAL(new String[]{"<="}),
    GREATER_THAN(new String[]{">"}),
    GREATER_THAN_EQUAL(new String[]{">="}),
    EXISTS(new String[]{"not null", "exists"}),
    NOT_EXISTS(new String[]{"is null", "not exists"}),
    MATCH(new String[]{"match"});

    private String[] value;

    Operator(String[] value) {
        this.value = value;
    }

    @JsonValue
    public String toStringOperator(){
        return value[0];
    }

    @JsonCreator
    public static Operator fromStringOperator(String stringOperator) {
        if(stringOperator != null) {
            for(Operator operator : Operator.values()) {
                for(String operatorString : operator.value) {
                    if (stringOperator.equalsIgnoreCase(operatorString)) {
                        return operator;
                    }
                }
            }
        }
        return null;
    }
}
Lizabethlizard answered 29/1, 2016 at 23:32 Comment(0)
S
5

There are various approaches that you can take to accomplish deserialization of a JSON object to an enum. My favorite style is to make an inner class:

import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonFormat;
import com.fasterxml.jackson.annotation.JsonProperty;
import org.hibernate.validator.constraints.NotEmpty;

import java.util.Arrays;
import java.util.Map;
import java.util.function.Function;
import java.util.stream.Collectors;

import static com.fasterxml.jackson.annotation.JsonFormat.Shape.OBJECT;

@JsonFormat(shape = OBJECT)
public enum FinancialAccountSubAccountType {
  MAIN("Main"),
  MAIN_DISCOUNT("Main Discount");

  private final static Map<String, FinancialAccountSubAccountType> ENUM_NAME_MAP;
  static {
    ENUM_NAME_MAP = Arrays.stream(FinancialAccountSubAccountType.values())
      .collect(Collectors.toMap(
        Enum::name,
        Function.identity()));
  }

  private final String displayName;

  FinancialAccountSubAccountType(String displayName) {
    this.displayName = displayName;
  }

  @JsonCreator
  public static FinancialAccountSubAccountType fromJson(Request request) {
    return ENUM_NAME_MAP.get(request.getCode());
  }

  @JsonProperty("name")
  public String getDisplayName() {
    return displayName;
  }

  private static class Request {
    @NotEmpty(message = "Financial account sub-account type code is required")
    private final String code;
    private final String displayName;

    @JsonCreator
    private Request(@JsonProperty("code") String code,
                    @JsonProperty("name") String displayName) {
      this.code = code;
      this.displayName = displayName;
    }

    public String getCode() {
      return code;
    }

    @JsonProperty("name")
    public String getDisplayName() {
      return displayName;
    }
  }
}
Soissons answered 31/8, 2016 at 22:5 Comment(0)
R
5

In the context of an enum, using @JsonValue now (since 2.0) works for serialization and deserialization.

According to the jackson-annotations javadoc for @JsonValue:

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.

So having the Event enum annotated just as above works (for both serialization and deserialization) with jackson 2.0+.

Ross answered 20/7, 2018 at 15:26 Comment(0)
G
3

Besides using @JsonSerialize @JsonDeserialize, you can also use SerializationFeature and DeserializationFeature (jackson binding) in the object mapper.

Such as DeserializationFeature.READ_UNKNOWN_ENUM_VALUES_USING_DEFAULT_VALUE, which give default enum type if the one provided is not defined in the enum class.

Grouch answered 13/3, 2019 at 18:34 Comment(0)
H
1

In my case, this is what resolved:

import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonFormat;
import com.fasterxml.jackson.annotation.JsonProperty;

@JsonFormat(shape = JsonFormat.Shape.OBJECT)
public enum PeriodEnum {

    DAILY(1),
    WEEKLY(2),
    ;

    private final int id;

    PeriodEnum(int id) {
        this.id = id;
    }

    public int getId() {
        return id;
    }

    public String getName() {
        return this.name();
    }

    @JsonCreator
    public static PeriodEnum fromJson(@JsonProperty("name") String name) {
        return valueOf(name);
    }
}

Serializes and deserializes the following json:

{
  "id": 2,
  "name": "WEEKLY"
}

I hope it helps!

Heterogeneous answered 10/3, 2020 at 14:39 Comment(0)
O
1

I did it like this :

// Your JSON
{"event":"forgot password"}

// Your class to map 
public class LoggingDto {
    @JsonProperty(value = "event")
    private FooEnum logType;
}

//Your enum
public enum FooEnum {

    DATA_LOG ("Dummy 1"),
    DATA2_LOG ("Dummy 2"),
    DATA3_LOG ("forgot password"),
    DATA4_LOG ("Dummy 4"),
    DATA5_LOG ("Dummy 5"),
    UNKNOWN ("");

    private String fullName;

    FooEnum(String fullName) {
        this.fullName = fullName;
    }

    public String getFullName() {
        return fullName;
    }

    @JsonCreator
    public static FooEnum getLogTypeFromFullName(String fullName) {
        for (FooEnum logType : FooEnum.values()) {
            if (logType.fullName.equals(fullName)) {
                return logType;
            }
        }
        return UNKNOWN;
    }


}

So the value of the property "logType" for class LoggingDto will be DATA3_LOG

Onetoone answered 2/7, 2020 at 9:55 Comment(0)
H
1
@JsonFormat(shape = JsonFormat.Shape.OBJECT)
public enum LoginOptionType {

 PHONE(1, "Phone"), MAIL(2, "mail"), PERSONAL_EMAIL(3, "Personal email");

private static List<LoginOptionType> all;

static {
    all = new ArrayList<LoginOptionType>() {
        {
            add(LoginOptionType.PHONE);
            add(LoginOptionType.MAIL);
            add(LoginOptionType.PERSONAL_EMAIL);
        }
    };
}

private final Integer viewValue;

private final String name;

LoginOptionType(Integer viewValue, String name) {
    this.viewValue = viewValue;
    this.name = name;
}

public Integer getViewValue() {
    return viewValue;
}

public String getName() {
    return name;
}

public static List<LoginOptionType> getAll() {
    return all;
}
}

Response

[
{
    "viewValue": 1,
    "name": "Phone"
},
{
    "viewValue": 2,
    "name": "mail"
},
{
    "viewValue": 3,
    "name": "Personal email"
}
]
Hasan answered 1/12, 2021 at 5:44 Comment(0)
T
1

Here, 'value' acts as a deserialiser and 'namespace' acts as a serialiser. Hence, you can pass in value "Student Absent" to API while saving, and in DB it will be saved as "STUDENT_ABSENT". On the other hand, while retrieving data in your class, your API will return "Student Absent"

import com.fasterxml.jackson.annotation.JsonProperty;
public enum AttendanceEnums {
    STUDENT_PRESENT,
    @JsonProperty(value = "Student Absent", namespace = "Student Absent")
    STUDENT_ABSENT;
}
Tightrope answered 28/9, 2022 at 10:45 Comment(0)
B
1

I had been looking for a solution to enum serialization and I finally made a solution.

https://github.com/sirgilligan/EnumerationSerialization

https://digerati-illuminatus.blogspot.com/2022/10/java-enum-generic-serializer-and.html

It uses a new annotation and two new classes, EnumerationSerializer and EnumerationDeserializer. You can subclass the EnumerationDeserializer and make a class that sets the enum Class (typical approach) or you can annotate the enum and you don't have to have a subclass of EnumerationDeserializer.

@JsonSerialize(using = EnumerationSerializer.class)
@JsonDeserialize(using = EnumerationDeserializer.class)
@EnumJson(serializeProjection = Projection.NAME, deserializationClass = RGB.class)
enum RGB {
    RED,
    GREEN,
    BLUE
}

Notice how the implementation of ContextualDeserializer pulls the class from the annotation.

https://github.com/sirgilligan/EnumerationSerialization/blob/main/src/main/java/org/example/EnumerationDeserializer.java

There is a lot of good code in this that might give insights.

For your specific question you could do this:

@JsonSerialize(using = EnumerationSerializer.class)
@JsonDeserialize(using = EnumerationDeserializer.class)
@EnumJson(serializeProjection = Projection.NAME, deserializationClass = Event.class)
public enum Event {
    FORGOT_PASSWORD("forgot password");

    //This annotation is optional because the code looks for value or alias.
    @EnumJson(serializeProjection = Projection.VALUE)
    private final String value;

    private Event(final String description) {
        this.value = description;
    }

}

Or you could do this:

@JsonSerialize(using = EnumerationSerializer.class)
@JsonDeserialize(using = EnumerationDeserializer.class)
@EnumJson(serializeProjection = Projection.NAME, deserializationClass = Event.class)
public enum Event {
    FORGOT_PASSWORD("forgot password");

    private final String value;

    private Event(final String description) {
        this.value = description;
    }

}

That's all you have to do.

Then if you have a class that "has a" event you can annotate each occurance to serialize the way you want.

class EventHolder {
    @EnumJson(serializeProjection = Projection.NAME)
    Event someEvent;

    @EnumJson(serializeProjection = Projection.ORDINAL)
    Event someOtherEvent;

    @EnumJson(serializeProjection = Projection.VALUE)
    Event yetAnotherEvent;
}
Bayle answered 21/10, 2022 at 23:34 Comment(0)
P
0

The simplest way I found is using @JsonFormat.Shape.OBJECT annotation for the enum.

@JsonFormat(shape = JsonFormat.Shape.OBJECT)
public enum MyEnum{
    ....
}
Papert answered 29/5, 2019 at 12:0 Comment(0)
C
0

This post is old, but if it can help someone, use JsonFormat.Shape.STRING

@JsonFormat(shape = JsonFormat.Shape.STRING)
public enum SomeEnum{
    @JsonProperty("SOME_PROPERTY")
    someProperty,
    ...
}

Code results is like this

{"someenum":"SOME_PROPERTY"}
Crowberry answered 27/8, 2021 at 13:5 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.