Skip to content
Advertisement

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?

Advertisement

Answer

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)));
User contributions licensed under: CC BY-SA
6 People found this is helpful
Advertisement