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 ...?
GsonBuilder.serializeNullshelp? (Literally a wild stab in the dark - I don't know if that affects deserialization behaviour)serializeNulls()is for excluding null fields while serializing, not when deserializing.