1

I need to avoid returning an array with null items after parsing a response from a Web Service, but I cannot figure out how to configure Gson to do so. I intended to do it this way as I don't like having to process the parsed response (having to loop the array again).

Let me show you a basic example of what I mean.

Given this POJO:

public class Item {
  @SerializedName("_id")
  private String id;  // Required field
  private String name;
  @SerializedName("accept_ads")
  private boolean acceptAds;

  // Getters and setters
}

And given a JSON response from a webservice:

[
  {"_id": "a01",
  "name": "Test1",
  "accept_ads": true},
  {"name": "Test2"}
]

I created this deserializer:

public class ItemDeserializer implements JsonDeserializer<Item> {

  @Override
  public Item deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException {
    Item item = null;

    JsonObject jsonObject = json.getAsJsonObject();
    if (jsonObject.has("_id")) {
      item = new Item();
      item.setId(jsonObject.get("_id").getAsString());
      if (jsonObject.has("name")) {
        item.setName(jsonObject.get("name").getAsString());
      }
      if (jsonObject.has("accept_ads")) {
        item.setAcceptAds(jsonObject.get("accept_ads").getAsBoolean());
      }
    }

    return item;
  }

}

Then, I create my Gson and Retrofit instances like this:

Gson gson = new GsonBuilder().registerTypeAdapter(Item.class, new ItemDeserializer()).create();

mRetrofit = new Retrofit.Builder().addConverterFactory(GsonConverterFactory.create(gson)).build();

Unfortunately, the returned ArrayList<Item> contains both an Item and a null value.

UPATE

Thanks to @deluxe1 in the comments below, I read the linked SO thread, which by itself isn't what I was looking for, as it's about serializers instead of deseralizers. However, another answer in that thread specifically focuses on deserializers.

Unfortunatedly, I'm unable to make it work. Please notice that, in that case, is about null JsonElements inside the JSON HTTP body response, which isn't exactly what I'm trying to achieve. In my case, a JsonElement might not be null, but lacking required fields which, in the end, makes it invalid (therefore, null).

Given the example classes above, I created the following deserializer:

public class NonNullListDeserializer<T> implements JsonDeserializer<List<T>> {

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

    Gson gson = new GsonBuilder().create();    
    List<T> items = new ArrayList<>();
    for (final JsonElement jsonElement : json.getAsJsonArray() {

      T item = gson.fromJson(jsonElement, typeOfT);
      if (item != null) {
        items.add(item);
      }

    }

    return items;

  }
}

Of course, I remembered to register it as a TypeAdapter in Gson.

The thing is, typeOfT is java.util.ArrayList<com.example.Item> when that method gets invoked (But why??). As a result, the line T item = gson.fromJson(jsonElement, typeOfT) doesn't get called with the inner class (Item, in this case), causing this exception:

com.google.gson.JsonSyntaxException: java.lang.IllegalStateException: Expected BEGIN_ARRAY but was BEGIN_OBJECT at path $

AFAIK, type erasure makes it impossible to do the following:

T item = gson.fromJson(jsonElement, T.class)
5
  • You are missing one "-char in the JSON: "a01, if that's what you are really returning, you should fix it first. Commented Mar 11, 2019 at 14:41
  • @vilpe89 Thanks, I've updated the example, as that was just a typo from my side. Also, please notice that the provided JSON is just a simple example to explain my issues. The returning JSON from the webservice is perfectly formed as well. Commented Mar 11, 2019 at 15:18
  • Take a look at this question and the accepted answer stackoverflow.com/a/29733208/9464051 Commented Mar 11, 2019 at 15:52
  • @UnaDeKalamares, have link from deluxe1 helped you? Do you need any other suggestions? Commented Mar 12, 2019 at 10:23
  • I was reviewing that answer, that seems to point in the right direction but doesn't seem to work. I'll update my question to include further information. Thanks! Commented Mar 12, 2019 at 10:54

1 Answer 1

0

NonNullListDeserializer requires small modifications:

class NonNullListDeserializer<T> implements JsonDeserializer<List<T>> {

    @Override
    public List<T> deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException {
        if (json instanceof JsonArray) {
            final JsonArray array = (JsonArray) json;
            final int size = array.size();
            if (size == 0) {
                return Collections.emptyList();
            }
            final List<T> list = new ArrayList<>(size);
            for (int i = 0; i < size; i++) {
                // get element type
                Type elementType = $Gson$Types.getCollectionElementType(typeOfT, List.class);
                T value = context.deserialize(array.get(i), elementType);
                if (value != null) {
                    list.add(value);
                }
            }

            return list;
        }

        return Collections.emptyList();
    }
}

Now, you need to deserialise your JSON payload as below:

Gson gson = new GsonBuilder()
        .registerTypeAdapter(Item.class, new ItemDeserializer())
        .registerTypeAdapter(List.class, new NonNullListDeserializer<>())
        .setPrettyPrinting()
        .create();

Type itemListType = new TypeToken<List<Item>>() {}.getType();
List<Item> response = gson.fromJson(json, itemListType);
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.