1

I have a return from the server which can come through as:

[{
    "id":"1",
    "objectOne": {
        "name":"jim"
    }
}, {
    "id":"1",
    "objectOne": [{
        "name": "jim1"
    }, {
        "name": "jim2"
    }
}, {
    "id":"1",
    "objectOne": null
}]

That is, one value can either be an object, an object array, or null.

I'm using Gson converter with Retrofit and I'm using this TypeAdapterFactory to force single objects to be read as an array:

    GsonBuilder gsonBuilder = new GsonBuilder();
    gsonBuilder.registerTypeAdapterFactory(new ObjectToArrayFactory());
    Gson gson = gsonBuilder.create();

Factory:

private class ObjectToArrayAdapter<T> extends TypeAdapter<List<T>> {

    Gson gson;
    private Class<T> adapterclass;

    public ObjectToArrayAdapter(Gson gson, Class<T> adapterclass) {
        this.gson = gson;
        this.adapterclass = adapterclass;
    }

    @Override
    public void write(JsonWriter out, List<T> value) throws IOException {}

    public List<T> read(JsonReader reader) throws IOException {
        List<T> list = new ArrayList<T>();

        if (reader.peek() == JsonToken.BEGIN_OBJECT) {
            // If it's meant to be an array and instead it's a single object, add it to a newly created list.
            parseObject(list, reader, gson);
        } else if (reader.peek() == JsonToken.BEGIN_ARRAY) {
            // Otherwise, if it is actually a list, manually parse each item and add it to the list
            parseArray(list, reader, gson);
        } else if(reader.peek() == JsonToken.NULL) {
            // However if the server gives a null object, just return null.
            return null;
        }

        return list;
    }

    private void parseArray(List<T> list, JsonReader reader, Gson gson) throws IOException {
        reader.beginArray();
        while (reader.hasNext()) {
            parseObject(list, reader, gson);
        }
        reader.endArray();
    }

    private void parseObject(List<T> list, JsonReader reader, Gson gson) throws IOException {
        T inning = gson.fromJson(reader, adapterclass);
        list.add(inning);
    }
}

My problem is that, when I ask Retrofit to parse the value as an Array:

private List<PaymentsOption> objectOne;

The Gson parser seems to get confused, when it get's to the section of the json which looks like this:

"objectOne": null

I've debugged and logged my way through the parsing and it seems it follows what amounts to this code path (For brevity, I've parse out the actual code):

if(reader.peek() == JsonToken.BEGIN_ARRAY) {
    reader.beginArray();
    while(reader.hasNext()) { // public void parseTag()
         if(reader.peek() == JsonToken.BEGIN_OBJECT) {
             T inning = gson.fromJson(reader, adapterclass); <-- Crashes here
          }
    }
    reader.endArray();
}

So, it shouldn't be "peeking" as a beginArray as it's "null". It also shouldn't allow a reader.beginArray() as it's still "null". It should peek again and see beginObject. It allows a reader.beginObject() inside of gson.fromJson but fails on reader.readName() as it's actually reading "null". Exception is as follows:

com.google.gson.JsonSyntaxException: java.lang.IllegalStateException: Expected a name but was NULL at line 24 column 39 path $[1].objectOne
10-27 12:05:20.452  E/Exception:     at com.google.gson.internal.bind.ReflectiveTypeAdapterFactory$Adapter.read(ReflectiveTypeAdapterFactory.java:200)
10-27 12:05:20.452  E/Exception:     at com.google.gson.Gson.fromJson(Gson.java:810)
10-27 12:05:20.452  E/Exception:     at uk.co.utils.network.ObjectToArrayFactory$ObjectToArrayAdapter.parseTag(ObjectToArrayFactory.java:70)

I don't understand why the reader.peek() is showing first a beginArray, allowing a reader.beginArray(), then showing a reader.peek() as a beginObject() and why it's allowing a reader.beginObject(). As far as I understand, it should have shown a reader.peek() == Json.Token.NULL ...?

4
  • Does GsonBuilder.serializeNulls help? (Literally a wild stab in the dark - I don't know if that affects deserialization behaviour) Commented Oct 27, 2015 at 10:34
  • As far as I can see, serializeNulls() is for excluding null fields while serializing, not when deserializing. Commented Oct 27, 2015 at 10:36
  • Hmm, this is working fine for me (Gson 2.3.1) - gist.github.com/alexcrt/4d65f0e0875a489466e9 Commented Oct 27, 2015 at 10:46
  • @AlexisC. I'm also using Gson 2.3.1, I can't see why yours works and mine doesn't, from what I can see we're logically doing things the same... Commented Oct 27, 2015 at 10:53

1 Answer 1

1

You need to write a TypeAdapter and register that when you are building your gson object. In your adapter's read method you can check whether the given parameter is either null or not, or empty and take action accordingly.Your read method will look like:

public Number read(JsonReader in) throws IOException{
 if(in.peek() == JsonToken.NULL) in.nextNull();
 try{
   //read value and take suitable action 
 }catch(Exception e){}
}

But you need to write a typeAdapter for every different data type that needs special treatment.

Sign up to request clarification or add additional context in comments.

2 Comments

I've been writing something similar which works for all objects, will post as an answer in a second if it works.
Nope, a TypeAdapterFactory which looks for nulls also doesn't seem to help. I've post the Adapter I created in my Question.

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.