Как обрабатывать параметры, которые могут быть ARRAY или OBJECT в Retrofit на Android?
У меня возникла проблема, когда обработчик API, который я обрабатываю, возвращает OBJECT для ARRAY размера 1.
Например, иногда API отвечает:
{
"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": {}
}
],
}
Однако, если массив monument
имеет только 1 элемент, он становится ОБЪЕКТОМ (обратите внимание на отсутствие скобок []
) так:
{
"monument": {
"key": 4152,
"name": "MTS - Corporate Head Office",
"categories": {},
"address": {}
}
}
Если я определяю свои модели следующим образом, я получаю сообщение об ошибке, когда возвращается только один элемент:
public class Locations {
public List<Monument> monument;
}
Если возвращается только один элемент, я получаю следующую ошибку:
Expected BEGIN_OBJECT but was BEGIN_ARRAY ...
И если я определяю свою модель следующим образом:
public class Locations {
public Monument monument;
}
и API возвращает ARRAY. Я получаю противоположную ошибку.
Expected BEGIN_ARRAY but was BEGIN_OBJECT ...
Я не могу определить несколько элементов с тем же именем в моей модели.
Как я могу справиться с этим случаем?
Примечание. Я не могу вносить изменения в API.
Ответы
Ответ 1
В качестве дополнения к моему предыдущему ответу, здесь решение с использованием TypeAdapter
.
public class LocationsTypeAdapter extends TypeAdapter<Locations> {
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;
}
}
Ответ 2
Трюк записывает ваш собственный десериализатор Gson для вашего класса Locations
. Это проверит, является ли элемент памятника объектом или массивом. Например:
public class LocationsDeserializer implements JsonDeserializer<Locations> {
@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");
}
}
}
Для удобства добавьте конструктор vararg в класс Locations
:
public class Locations {
public List<Monument> monuments;
public Locations(Monument ... ms) {
monuments = Arrays.asList(ms);
}
}
Ваш класс Памяти остается прежним. Что-то вроде:
public class Monument {
public int key;
public String name;
// public Categories categories;
// public Address address;
}
Наконец, создайте свой собственный объект Gson
и передайте его в модификацию RestAdapter
.
Gson gson = new GsonBuilder().registerTypeAdapter(Locations.class, new LocationsDeserializer()).create();
RestAdapter restAdapter = new RestAdapter.Builder()
.setEndpoint(baseUrl)
.setConverter(new GsonConverter(gson))
.build();
Ответ 3
Вы можете зарегистрировать TypeAdapter
с помощью Gson, который условно обрабатывает это поведение.
Вы сначала назовёте peekToken()
. Если он был BEGIN_ARRAY
, тогда просто десериализуем как List<Foo>
. Если он BEGIN_OBJECT
, то десериализуйтесь как Foo
и заверните в Collections.singletonList
.
Теперь у вас всегда есть список, будь то один элемент или много.