0

I have 2 implementations of Value interface, RangeValue, FileValue. RangeValue looks like below:

    public class RangeValue implements Value {
    private int min;
    private int max;
    
    public RangeValue(int min, int max) {
        this.min = min;
        this.max = max;
    }
    
    public int getMin() {
        return min;
    }
    
    public int getMax() {
        return max;
    }
}

FileValue looks like below:

public class FileValue implements Value {
    private String contentType;
    private String value;
    
    public FileValue(String contentType, String value) {
        this.contentType = contentType;
        this.value = value;
    }

    public String getContentType() {
        return contentType;
    }

    public String getValue() {
        return value;
    }
}

the json for RangeValue looks like :

    {
    "min": 200,
    "max": 300
}

The json for FileValue looks:

{
    "contentType": "application/octet-stream",
    "value": "fileValue"
}

Now I want the RequestType parameter for these json to be of type Value only, I can't change the JSON files i.e. the json would look like the same and user should use the same JSON in request body as stated above.

I solved this by using @JsonTypeInfo & @JsonSubTypes by adding extra attributes to the above JSON i.e. type but the spec doesn't allow me to add that.

How can the appropriate concrete class could be instantiated based on the JSON above without altering?

2
  • Your interface Value does not define any methods which are implemented by RangeValue and FileValue. So what is the purpose of this interface? Commented Feb 16, 2023 at 9:08
  • You are right here, but there will be a method getAttributes() which should be implemented by RangeValue and FileValue. Currently, I need help with how can I solve my problem here. Commented Feb 16, 2023 at 9:19

1 Answer 1

1

Option 1: custom deserializer. Algorithm can be as follows:

  1. Parse to JsonNode.
  2. Use the properties in the node to find the correct class to deserialize into.
  3. Convert the node to instance of the actual class.

Simplified example:

public class ValueDeserializer extends StdDeserializer<Value> {

  public ValueDeserializer() {
    super(Value.class);
  }

  @Override
  public Value deserialize(JsonParser parser, DeserializationContext context) throws IOException {
    JsonNode root = parser.readValueAsTree();
    if (root instanceof ObjectNode objectNode) {
      JsonNode valueNode = objectNode.get("somePropertyName");
      Class<? extends Value> clazz = valueNode == null ? RangeValue.class : FileValue.class;
      return context.readTreeAsValue(objectNode, clazz);
    }
    throw new JsonParseException(parser, "not an object");
    //handling the case, when json is json array
    //or something else which can't be deserialized into object
  }
}

Register the deserializer with JsonDeserialize on the interface:

@JsonDeserialize(using = ValueDeserializer.class)

Put the same annotation on RangeValue and FileValue, without specifying a deserializer, otherwise you will get StackOverflowError.

Option 2: use JsonTypeInfo.Id.DEDUCTION

@JsonSubTypes({
        @JsonSubTypes.Type(FileValue.class),
        @JsonSubTypes.Type(RangeValue.class)
})
@JsonTypeInfo(use = JsonTypeInfo.Id.DEDUCTION)
public interface Value {
}

Jackson will deduce the correct class using the property names. Keep in mind exception will be thrown if it fails deduction.

Means that no serialized typing-property is used. Types are deduced based on the fields available. Deduction is limited to the names of fields (not their values or, consequently, any nested descendants). Exceptions will be thrown if not enough unique information is present to select a single subtype. If deduction is being used annotation properties visible, property and include are ignored.

Sign up to request clarification or add additional context in comments.

3 Comments

Thanks for the answer, however here I need to provide somePropertyName and this is not flexible because the implementation could grow and then I have to extend this each time. Is there a flexible way where it auto-detects the concrete class based on the attribute in the provided JSON?
@mdanish98 Check second option with deduction. That's probably the most auto detection we can get.
the second option is really nice and working as a charm except there is demerit that if two classes have same attribute name it fails but I know the workaround. thank you for this :)

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.