7

I have an interface Event and multiple enums that implement this interface (UserEvent, BusinessEvent, etc).

I want to deserialize the following json data:

{
  "event" : "SIGNUP"
}

To this bean:

public class Input
{ 
   private Event event;

   public Event getEvent() {..}
   public void setEvent(Event event) {..}
}

public enum UserEvent implements Event
{
    SIGNUP;
}

Here, i'd like event to be deserialized to UserEvent.SIGNUP.

How can I accomplish this? Reading up on @JsonTypeInfo seems to indicate that an additional type attribute would be needed, but in this case, there's just one string which maps directly to an enum value.

Nicolas Filotto
  • 43,537
  • 11
  • 94
  • 122
Ali
  • 261,656
  • 265
  • 575
  • 769

3 Answers3

4

You are using Event interface for field event in Input and jackson doesn't know anything about UserEvent as implementation of this interface.

You can use custom JsonDeserializer to get value:

public interface Event {
}

public static class Input
{
    private Event event;

    @JsonDeserialize(using = EventDeserializer.class)
    public Event getEvent() {
        return event;
    }

    public void setEvent(Event event) {
        this.event = event;
    }
}

public enum UserEvent implements Event
{
    SIGNUP;
}

public static class EventDeserializer  extends StdDeserializer<Event> {

    protected EventDeserializer() {
        super(Event.class);
    }

    @Override
    public Event deserialize(JsonParser p, DeserializationContext ctxt) throws IOException, JsonProcessingException {
        return UserEvent.valueOf(p.getText());
    }
}

@Test
public void converEnum() throws Exception {
    ObjectMapper objectMapper = new ObjectMapper();
    Input input = objectMapper.readValue("{\n" +
            "  \"event\" : \"SIGNUP\"\n" +
            "}", Input.class);

    Assert.assertThat(input.getEvent(), Matchers.is(UserEvent.SIGNUP));
}
Vlad Bochenin
  • 3,007
  • 1
  • 20
  • 33
  • 2
    Does this solution back you into the corner of only being able to use a single concrete implementation for the `Event` field? Since you annotate the getter for the `Event` field with the `UserEventDeserializer`, what happens if the concrete value in the Event field is a `BusinessEvent`? – mfunaro Mar 31 '21 at 17:50
  • 1
    @mfunaro I don't think so. Even if example is just for UserEvent nothing will stop you to write more sofisticated code with possible enums values in 'deserilize' method. – Vlad Bochenin Apr 01 '21 at 14:47
2

For a single enum the jsonschema2pojo-maven-plugin generates code like the following:

@JsonCreator
public static Foo fromValue(String value) {
    Foo constant = CONSTANTS.get(value);
    if (constant == null) {
        throw new IllegalArgumentException(value);
    } else {
        return constant;
    }
}

I guess you could write a factory method which is annotated with @JsonCreator and somehow decides which enum to choose. I'm not sure if it matters much where you put this method.

Puce
  • 37,247
  • 13
  • 80
  • 152
2

Having a similar task I ended up creating a custom deserializer:

public class IFooDeserializer extends StdDeserializer<IFoo> {

    public IFooDeserializer() {
        this(null);
    }

    public IFooDeserializer(Class<?> vc) {
        super(vc);
    }

    @Override
    public IFoo deserialize(JsonParser p, DeserializationContext ctxt) throws IOException, JsonProcessingException {
        String value = p.getCodec().readValue(p, String.class);
        return Stream.of(GoodFoo.values(), BadFoo.values())
                .flatMap(Stream::of)
                .filter(el -> el.name().equals(value))
                .findAny()
                .orElseThrow(() -> new RuntimeException("Could not deserialize foo: " + value));
    }
}
Zaziro
  • 405
  • 4
  • 8