8

I have some odd JSON like:

[
  {
    "type":"0",
    "value":"my string"
  },
  {
    "type":"1",
    "value":42
  },
  {
    "type":"2",
    "value": {
    }
  }
]

Based on some field, the object in the array is a certain type. Using Gson, my thought is to have a TypeAdapterFactory that sends delegate adapters for those certain types to a TypeAdapter, but I'm hung up on understanding a good way of reading that "type" field to know which type to create. In the TypeAdapter,

Object read(JsonReader in) throws IOException {
  String type = in.nextString();
  switch (type) {
    // delegate to creating certain types.
  }
}

would assume the "type" field comes first in my JSON. Is there a decent way to remove that assumption?

1 Answer 1

10

Here is some code I wrote to handle an array of NewsFeedArticle and NewsFeedAd items in Json. Both items implement a marker interface NewsFeedItem to allow me to easily check if the TypeAdater should be used for a particular field.

public class NewsFeedItemTypeAdapterFactory implements TypeAdapterFactory {

    @Override
    @SuppressWarnings("unchecked")
    public <T> TypeAdapter<T> create(Gson gson, TypeToken<T> type) {
        if (!NewsFeedItem.class.isAssignableFrom(type.getRawType())) {
            return null;
        }

        TypeAdapter<JsonElement> jsonElementAdapter = gson.getAdapter(JsonElement.class);
        TypeAdapter<NewsFeedArticle> newsFeedArticleAdapter = gson.getDelegateAdapter(this, TypeToken.get(NewsFeedArticle.class));
        TypeAdapter<NewsFeedAd> newsFeedAdAdapter = gson.getDelegateAdapter(this, TypeToken.get(NewsFeedAd.class));

        return (TypeAdapter<T>) new NewsFeedItemTypeAdapter(jsonElementAdapter, newsFeedArticleAdapter, newsFeedAdAdapter).nullSafe();
    }

    private static class NewsFeedItemTypeAdapter extends TypeAdapter<NewsFeedItem> {

        private final TypeAdapter<JsonElement> jsonElementAdapter;
        private final TypeAdapter<NewsFeedArticle> newsFeedArticleAdapter;
        private final TypeAdapter<NewsFeedAd> newsFeedAdAdapter;

        NewsFeedItemTypeAdapter(TypeAdapter<JsonElement> jsonElementAdapter,
                TypeAdapter<NewsFeedArticle> newsFeedArticleAdapter,
                TypeAdapter<NewsFeedAd> newsFeedAdAdapter) {
            this.jsonElementAdapter = jsonElementAdapter;
            this.newsFeedArticleAdapter = newsFeedArticleAdapter;
            this.newsFeedAdAdapter = newsFeedAdAdapter;
        }

        @Override
        public void write(JsonWriter out, NewsFeedItem value) throws IOException {
            if (value.getClass().isAssignableFrom(NewsFeedArticle.class)) {
                newsFeedArticleAdapter.write(out, (NewsFeedArticle) value);

            } else if (value.getClass().isAssignableFrom(NewsFeedAd.class)) {
                newsFeedAdAdapter.write(out, (NewsFeedAd) value);

            }

        }

        @Override
        public NewsFeedItem read(JsonReader in) throws IOException {
            JsonObject objectJson = jsonElementAdapter.read(in).getAsJsonObject();

            if (objectJson.has("Title")) {
                return newsFeedArticleAdapter.fromJsonTree(objectJson);

            } else if (objectJson.has("CampaignName")) {
                return newsFeedAdAdapter.fromJsonTree(objectJson);
            }

            return null;
        }
    }
}

You can then register this with Gson using the following code.

return new GsonBuilder()
        .registerTypeAdapterFactory(new NewsFeedItemTypeAdapterFactory())
        .create();
Sign up to request clarification or add additional context in comments.

Comments

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.