4

Say I have a class structure like this :-

class ShapeRequest {

    ShapeInfo shapeInfo;
    Shape shape;

    static class ShapeInfo {
        String shapeName;
        String shapeDimension;
    }

    static abstract class Shape {

    }

    static class Square extends Shape{
        int area;
    }

    static class Circle extends Shape{
        int area;
    }
}

How can I deserialize the ShapeRequest in a way that field shape, is mapped to Square or Circle type, depending on the shapeInfo.shapeName field value?

For example, the following JSON should map to ShapeRequest with Circle shape type, because shapeInfo.shapeName = "circle"

{
  "shapeInfo": {
    "shapeName": "circle",
    "shapeDimension": "2"
  },
  "shape": {
    "area": 10
  }
}
Jerald Baker
  • 1,121
  • 1
  • 12
  • 48

1 Answers1

0

You can use it below:

public class JsonTypeExample {

    // Main method to test our code.
    public static void main(String args[]) throws IOException {
        ObjectMapper objectMapper = new ObjectMapper();

        // json to circle based on shapeName
        String json = "{\"shapeName\":\"circle\",\"area\":10}";
        Shape shape = objectMapper.readerFor(Shape.class).readValue(json);
        System.out.println(shape.getClass());
        System.out.println(objectMapper.writeValueAsString(shape));

        // json to square based on shapeName
        json = "{\"shapeName\":\"square\",\"area\":10}";
        shape = objectMapper.readerFor(Shape.class).readValue(json);
        System.out.println(shape.getClass());
        System.out.println(objectMapper.writeValueAsString(shape));
    }

    @JsonTypeInfo(use = JsonTypeInfo.Id.NAME, include = JsonTypeInfo.As.PROPERTY, property = "shapeName")
    @JsonSubTypes({
            @JsonSubTypes.Type(value = Square.class, name = "square"),
            @JsonSubTypes.Type(value = Circle.class, name = "circle")
    })
    static class Shape {
        Shape() {
        }
    }

    @JsonTypeName("square")
    static class Square extends Shape {
        public int area;

        Square(int area){
            super();
            this.area = area;
        }
    }

    @JsonTypeName("circle")
    static class Circle extends Shape {
        public int area;

        Circle(int area){
            super();
            this.area = area;
        }
    }
}

Here Shape class is annotated with JsonTypeInfo and JsonSubTypes.

@JsonTypeInfo is used to indicate details of type information which is to be included in serialization and de-serialization. Here property signifies the value to be considered for determining the SubType.

@JsonSubTypes is used to indicate subtypes of types annotated. Here name and value maps the shapeName to appropriate SubType class.

Whenever a JSON is passed as in the example, de-serialization happens via JsonSubTypes, then it gives back the appropriate JAVA object based on shapeName.

Suman
  • 818
  • 6
  • 17
  • Thanks for your response. However, you're missing out the whole point of deserializing to class ShapeRequest {} which contains shapeInfo and the actual shape – Jerald Baker Aug 11 '20 at 18:30
  • Can you share an example of how you want to serialize/de-serialize the ShapeRequest? – Suman Aug 11 '20 at 18:35
  • Looks like you are not framing the problem statement right. Its not ideal to keep type signifier in a different place. Try to move that within shape. – Suman Aug 11 '20 at 18:59
  • Yeah. But Unfortunately, that's how it comes from an external system. – Jerald Baker Aug 11 '20 at 19:27
  • I tried a lot to achieve what you are looking for but it seems impossible. You can max set the shape to circle/square but can't set the area. This can be achieved using explicit mapper: new ObjectMapper().readerFor(Shape.class) .readValue(new ObjectMapper().writeValueAsString( ImmutableMap.of("shapeName", shapeInfo.getShapeName(), "area", 0))).. This should be called within shapeInfo setter and ignore shape setter within ShapeRequest. I feel this is not easily achievable as the problem statement is against what JsonSubTypes support. – Suman Aug 11 '20 at 19:56
  • One last suggestion, copying the shapeName inside shape as well, will solve your problem. Then you Java object will look like this - ShapeRequest(shapeInfo=ShapeInfo(shapeName=circle, shapeDimension=2), shape=Circle(area=10)). – Suman Aug 11 '20 at 20:13
  • Thank you for your time Suman. However I have no control over the request Json. As of now, i am manually traversing the shapeInfo JsonNode and then casting the shape depending on what i find in the node. – Jerald Baker Aug 12 '20 at 11:02