Handling Arbitary/Mixed Types in a Json Array with Gson

It's quite normal in Javascript land to have an array of mixed types, this causes some headaches when deserialising to Java objects. Here's a solution using a custom deserialiser in Gson.

Given the Json representing some ui cards:

{
  "results": [
    {
      "type": "foo",
      "unique_foo_field": {
        "foo": "foo"
      }
    },
    {
      "type": "bar",
      "unique_bar_field": {
        "bar": "bar"
      }
    },
    {
      "type": "molly",
      "unique_molly_field": {
        "molly": "molly"
      }
    },
    {
      "type": "wilma",
      "unique_wilma_field": {
        "wilma": "wilma"
      }
    }
  ]
}

Create a base object Card and a subclass for each type:

public class Card {
  public static final String TYPE_FOO = "foo";
  public static final String TYPE_BAR = "foo";
  public static final String TYPE_MOLLY = "molly";
  public static final String TYPE_WILMA = "wilma";
  public String type;
}

public class Foo extends Card {

  public FooField unique_foo_field;

  public class FooField{
    public String foo;
  }
}

public class Bar extends Card {

  public BarField unique_bar_field;

  public class BarField{
    public String bar;
  }
}

public class Molly extends Card {

  public MollyField unique_molly_field;

  public class MollyField{
    public String molly;
  }
}

public class Wilma extends Card {

  public WilmaField unique_wilma_field;

  public class WilmaField{
    public String wilma;
  }
}

The follow class inspects the type field and deserialises the approprite object:

package evestor.evestorapi;

import com.google.gson.JsonDeserializationContext;
import com.google.gson.JsonDeserializer;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.google.gson.JsonParseException;
import java.lang.reflect.Type;
import java.util.ArrayList;
import java.util.List;

import fisk.objects.Card;
import fisk.objects.Foo;
import fisk.objects.Bar;
import fisk.objects.Molly;
import fisk.objects.Wilma;

public class CardTypeAdapter implements JsonDeserializer<List<Card>> {

  private static final String CARD_TYPE = "type";

  @Override
  public List<Card> deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException {

    List<Card> cards = new ArrayList<>();

    if (json.isJsonArray()) {
      for (JsonElement cardElement : json.getAsJsonArray()) {

        JsonObject rawJsonObject = cardElement.getAsJsonObject();
        String cardType = rawJsonObject.get(CARD_TYPE).getAsString();

        switch(cardType){
          case Card.TYPE_FOO:
            cards.add((Foo) context.deserialize(cardElement, Foo.class));
            break;
          case Card.TYPE_BAR:
            cards.add((Bar) context.deserialize(cardElement, Bar.class));
            break;
          case Card.TYPE_MOLLY:
            cards.add((Molly) context.deserialize(cardElement, Molly.class));
            break;
          case Card.TYPE_WILMA:
            cards.add((Wilma) context.deserialize(cardElement, Wilma.class));
            break;
          default:
            Log.d("CardTypeAdapter", String.format("Skipping unrecognised card type: %s", cardType));
        }
      }
    } else {
      throw new RuntimeException("Unexpected JSON type (expected Array): " + json.getClass());
    }
    return cards;
  }
}

Create Gson instance with a custom type adapter:

GsonBuilder builder = new GsonBuilder();
Type cardListType = new TypeToken<List<Card>>(){}.getType();
builder.registerTypeAdapter(cardListType, new CardTypeAdapter());
Gson gson = builder.create();