Come gestire i parametri che possono essere un ARRAY o OBJECT in Retrofit su Android?

Sto riscontrando un problema in cui l’API che sto analizzando restituisce un OBJECT per un ARRAY di dimensione 1.

Ad esempio, a volte l’API risponderà con:

{ "monument": [ { "key": 4152, "name": "MTS - Corporate Head Office", "categories": {}, "address": {} }, { "key": 4151, "name": "Canadian Transportation Agency", "categories": {}, "address": {} }, { "key": 4153, "name": "Bank of Montreal Building", "categories": {}, "address": {} } ], } 

Tuttavia, se l’array del monument ha solo 1 elemento diventa un OBJECT (notare la mancanza di parentesi [] ) in questo modo:

 { "monument": { "key": 4152, "name": "MTS - Corporate Head Office", "categories": {}, "address": {} } } 

Se definisco i miei modelli in questo modo, ricevo un errore quando viene restituito un solo object:

 public class Locations { public List monument; } 

Se viene restituito solo un singolo object, ottengo il seguente errore:

 Expected BEGIN_OBJECT but was BEGIN_ARRAY ... 

E se definisco il mio modello in questo modo:

 public class Locations { public Monument monument; } 

e l’API restituisce un ARRAY. Ottengo l’errore opposto

 Expected BEGIN_ARRAY but was BEGIN_OBJECT ... 

Non riesco a definire più oggetti con lo stesso nome nel mio modello. Come posso gestire questo caso?

Nota: non posso apportare modifiche all’API.

Come complemento alla mia risposta precedente, ecco una soluzione che utilizza un TypeAdapter .

 public class LocationsTypeAdapter extends TypeAdapter { private Gson gson = new Gson(); @Override public void write(JsonWriter jsonWriter, Locations locations) throws IOException { gson.toJson(locations, Locations.class, jsonWriter); } @Override public Locations read(JsonReader jsonReader) throws IOException { Locations locations; jsonReader.beginObject(); jsonReader.nextName(); if (jsonReader.peek() == JsonToken.BEGIN_ARRAY) { locations = new Locations((Monument[]) gson.fromJson(jsonReader, Monument[].class)); } else if(jsonReader.peek() == JsonToken.BEGIN_OBJECT) { locations = new Locations((Monument) gson.fromJson(jsonReader, Monument.class)); } else { throw new JsonParseException("Unexpected token " + jsonReader.peek()); } jsonReader.endObject(); return locations; } } 

Il trucco è scrivere il tuo deserializzatore Gson per la tua class Locations . Questo controllerebbe se l’elemento monument è un object o un array. Così:

 public class LocationsDeserializer implements JsonDeserializer { @Override public Locations deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException { JsonElement monumentElement = json.getAsJsonObject().get("monument"); if (monumentElement.isJsonArray()) { return new Locations((Monument[]) context.deserialize(monumentElement.getAsJsonArray(), Monument[].class)); } else if (monumentElement.isJsonObject()) { return new Locations((Monument) context.deserialize(monumentElement.getAsJsonObject(), Monument.class)); } else { throw new JsonParseException("Unsupported type of monument element"); } } } 

Per comodità, aggiungi un costruttore di vararg alla tua class Locations :

 public class Locations { public List monuments; public Locations(Monument ... ms) { monuments = Arrays.asList(ms); } } 

La tua class Monument rimane uguale. Qualcosa di simile a:

 public class Monument { public int key; public String name; // public Categories categories; // public Address address; } 

Infine, crea il tuo object Gson e Gson al retrofit RestAdapter .

 Gson gson = new GsonBuilder().registerTypeAdapter(Locations.class, new LocationsDeserializer()).create(); RestAdapter restAdapter = new RestAdapter.Builder() .setEndpoint(baseUrl) .setConverter(new GsonConverter(gson)) .build(); 

È ansible registrare un TypeAdapter con Gson che gestisce questo comportamento in modo condizionale.

peekToken() chiameresti peekToken() . Se era BEGIN_ARRAY deserializza come List . Se è BEGIN_OBJECT deserializzare come Foo e avvolgere in Collections.singletonList .

Ora hai sempre un elenco se si tratta di un singolo object o di molti.