Search code examples
javajsonjacksonpolymorphism

Jackson subtypes - how to specify a default


I want to do something like this with subtypes. I have 3 different types of objects:

{
  "value": "string"
}

{
  "value": {
    "type": "obj1"
  }
}

{
  "value": {
    "type": "obj2"
  }
}

value can either be a string or an object.

The corresponding Java classes are

public interface Value {
}

public class ValueString implements Value {
    String value;
}

public abstract class ValueObj implements Value{
    public String type;
}

public class ValueObj1 extends ValueObj {
    private Obj1 value;
}

public class ValueObj2 extends ValueObj {
    private Obj2 value;
}

I don't mind having a discriminator inside Obj1 and Obj2, but there is no place for one when the value is just a string. Is there a way that I can set this up so that if the value is a string, it deserializes to ValueString, but if it is an object, it deserializes to the correct ValueObj1 or ValueObj2?


Solution

  • It can be easily done by creating a custom deserializer first:
    p.s. I assumed that there're only three types of objects as you posted.

    public class ValueDeserializer extends StdDeserializer<Value> {
        public ValueDeserializer() {
            this(null);
        }
    
        protected ValueDeserializer(Class<?> vc) {
            super(vc);
        }
    
        @Override
        public Value deserialize(JsonParser jsonParser, DeserializationContext deserializationContext) throws IOException, JacksonException {
            JsonNode jsonNode = jsonParser.getCodec().readTree(jsonParser);
            if (jsonNode.get("value").isValueNode()) {
                return new ValueString(jsonNode.get("value").asText());
            } else if ("obj1".equals(jsonNode.get("value").get("type").asText())) {
                ValueObj1 valueObj1 = new ValueObj1();
                    
                // The logic to handle type obj1
                    
                return valueObj1;
            } else {
                ValueObj2 valueObj2 = new ValueObj2();
    
                // The logic to handle type obj2
                    
                return valueObj2;
            }
    }
    

    Then simply annotate class Value with @JsonDeserialize as follows:

    @JsonDeserialize(using = ValueDeserializer.class)
    public interface Value {
    }
    

    Finally, let Jackson do the rest for you:

    ObjectMapper objectMapper = new ObjectMapper();
    System.out.println(objectMapper.writeValueAsString(objectMapper.readValue(jsonStr, Value.class)));