Retrofit GSON response abstract mapping

I make a simple HTTP GET request with retrofit and try to map the json response to my model. The thing is, the json return an array of multiples Shapes, the Shape is an abstract class, so it could be a Square, Circle, etc. Every shape has his own specified model, so different fields. How can I map this array of Shape to the model?

The web service json response

{
  "requestId": 0,
  "totalShapes": 2,
  "shapes": [
    {
      "circle": {
        "code": 1,
        "radius": 220
        "color" : "blue"
      }
    },
    {
      "square": {
        "code": 1,
        "size": 220
      }
    }
  ]
}

Main result mapping :

public class Result {
  @SerializedName("requestId") private int requestId;
  @SerializedName("totalShapes") private int totalShapes;
  @SerializedName("shapes") private List<Shape> shapes;
}

Abstract class :

public abstract class Shape implements Serializable {
}

Circle :

public class Circle {
  @SerializedName("code") private int code;
  @SerializedName("radius") private int radius;
  @SerializedName("color") private String color;
  // + getters...
}

Square :

public class Square {
  @SerializedName("code") private int code;
  @SerializedName("size") private int size;
  // + getters...
}

Answers


You may implement a custom shape deserializer that acts like a factory. Based on the key for a shape object, you can deserialize it to its corresponding type.

class ShapeDeserializer implements JsonDeserializer<Shape> {
    @Override
    public Shape deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException {
        Map.Entry<String, JsonElement> entry = json.getAsJsonObject().entrySet().iterator().next();
        switch(entry.getKey()) {
            case "circle":
                return context.deserialize(entry.getValue(), Circle.class);
            case "square":
                return context.deserialize(entry.getValue(), Square.class);
            default:
                throw new IllegalArgumentException("Can't deserialize " + entry.getKey());
        }
    }
}

Then you register it in your parser

Gson gson = 
    new GsonBuilder().registerTypeAdapter(Shape.class, new ShapeDeserializer())
                     .create();

and you use it:

Result result = gson.fromJson(myJson, Result.class);
//Result{requestId=0, totalShapes=2, shapes=[Circle{code=1, radius=220, color='blue'}, Square{code=2, size=220}]}

If the key matches exactly the class name, you might use Class.forName directly instead (you'd need to capitalize the key in first place).

Also note that:

  • you could move the code property into the abstract Shape class if it appears in every subclasses
  • I assumed that you have one and only one shape associated with a key. If you can have two circles in the same JsonObject associated with the key "circle" you'd need a more complex logic to parse it. If it's not the case (although it's not recommend by the RFC to have different key-value pairs with the same key) it should work fine.

Need Your Help

1 duplicate symbol for architecture i386

iphone objective-c xcode

I am facing a critical problem here, Xcode throws strange exception while building it's

Curl returns empty string

php curl

I have rather strange issue. I have an access to site(email and password). Sorry, but I can not show this site to you. I need to get some info from it's content.