Home > Enterprise >  Deserialize to dynamic enum with Jackson
Deserialize to dynamic enum with Jackson

Time:12-13

I have a Pojo that looks something like this

public class MyObject {
private MyEnum enum
}

And it works fine for de-serializing a Json object where the string value of 'enum' (in the Json) is a valud enum object.

Now, I have a new requirement that requires the enum to be determined dynamically. In other words, I have 2 types of MyObjects. They each use a different set of enum values. So I now have 2 enums, MyEnum1 and MyEnum2. When I read in a Json object, I know which type it is. Since an Enum class can't really be instantiated, I don't think I can use a factory method to determine which one to use. But not sure how to handle the de-serialization. I thought of using a generic, such as

public class MyObject<T> {
private T enum
}

But that doesn't really work since generics can't be specified at runtime.

Is there some other solution?

CodePudding user response:

The question is missing some context about how the enum field is used, but assuming you can alter the classes/enums, one solution could be to use an interface that both MyEnum1 and MyEnum2 implement. Like this:

public interface MyEnumType {
}

public enum MyEnum1 implements MyEnumType {
}

public enum MyEnum2 implements MyEnumType {
}

public class MyObject {

    private MyEnumType myEnum;
}

With that basic structure, we can use Jackson's @JsonTypeInfo and @JsonSubStypes annotations to achieve polymorphism in serialization/deserialization. Here's the annotated interface:

@JsonTypeInfo(use = Id.NAME)
@JsonSubTypes({ @Type(MyEnum1.class), @Type(MyEnum2.class) })
public interface MyEnumType {
}

Here is how I test that it's working:

import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;

import lombok.AllArgsConstructor;
import lombok.NoArgsConstructor;


@AllArgsConstructor
@NoArgsConstructor
public class MyObject {

    @JsonProperty
    private MyEnumType myEnum;

    @Override
    public String toString() {
        return "Enum value is "   myEnum;
    }

    public static void main(String[] args) throws JsonProcessingException {
        ObjectMapper jsonMapper = new ObjectMapper();

        MyObject original = new MyObject(MyEnum1.Blue);
        //MyObject original = new MyObject(MyEnum2.Triangle);
        String json = jsonMapper.writeValueAsString(original);

        MyObject result = jsonMapper.readValue(json, MyObject.class);
        System.out.println(result);
    }
}

Important note: this solution assumes that you can control the JSON format and both serialization and deserialization.

If you can not alter the JSON format or control the serialization side, you can instead use @JsonCreator to write a factory method that converts from the string JSON value to a matching instance of MyEnumType. For example, something like this:

public interface MyEnumType {

    @JsonCreator
    public static MyEnumType deserailize(String value) {
        MyEnumType result = null;

        try {
            result = MyEnum1.valueOf(value);
        } catch (IllegalArgumentException ex) {}

        if (result == null) {
            try {
                result = MyEnum2.valueOf(value);
            } catch (IllegalArgumentException ex) {}
        }

        if (result == null) {
            throw new IllegalArgumentException(value   " does not match any known enum.");
        }
        return result;
    }
}

This is a somewhat naive implementation; if you have more enum types you probably would want to make the implementation a little cleaner than just repeating the same try...catch again and again. But this should give a good starting point.

  • Related