Graeme
Graeme

Reputation: 25864

Gson parsing "null" objects as Array/Object?

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 ...?

Upvotes: 1

Views: 1475

Answers (1)

YamYamm
YamYamm

Reputation: 401

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.

Upvotes: 1

Related Questions