GSON игнорирует элементы с неправильным типом
Я использую Retrofit (в сочетании с OkHttp и GSON), чтобы общаться с онлайн-сервисом.
Webservice имеет обертку по умолчанию для всех ответов, аналогичную:
{
"resultCode":"OK",
"resultObj":"Can be a string or JSON object / array",
"error":"",
"message":""
}
В этом примере resultCode
будет либо OK
, либо NO
. Более того, error
и message
имеют только содержимое, когда произошла ошибка при обработке запроса. И последнее, но не менее важное: resultObj
будет содержать фактический результат вызова (который является строкой в примере, но некоторые вызовы возвращают массив JSON или объект JSON).
Чтобы обработать эти метаданные, я создал общий класс, например этот:
public class ApiResult<T> {
private String error;
private String message;
private String resultCode;
private T resultObj;
// + some getters, setters etcetera
}
Я также создал классы, которые представляют ответы, иногда заданные в resultObj
, и я определил интерфейс для использования с Retrofit, который выглядит примерно так:
public interface SomeWebService {
@GET("/method/string")
ApiResult<String> someCallThatReturnsAString();
@GET("/method/pojo")
ApiResult<SomeMappedResult> someCallThatReturnsAnObject();
}
Пока запрос действителен, все работает нормально. Но когда ошибка возникает на стороне сервера, она все равно вернет resultObj
с типом String. Это приводит к сбою someCallThatReturnsAnObject
внутри библиотеки RetoFit RestAdapter/GSON с сообщением типа:
retrofit.RetrofitError: com.google.gson.JsonSyntaxException: java.lang.IllegalStateException:
Ожидаемый BEGIN_OBJECT, но был STRING в строке 1 столбец 110 путь $.resultObj
Теперь, наконец, мои вопросы:
- Есть ли (простой) способ сообщить GSON, что он должен просто игнорировать (иначе называемый "nullify" ) свойство, если оно не соответствует ожидаемому типу?
- Могу ли я сказать GSON, чтобы обрабатывать пустые строки как null?
Ответы
Ответ 1
Определите свою модель следующим образом:
public class ApiResult {
private String error;
private String message;
private String resultCode;
private MyResultObject resultObj;
}
Затем создайте TypeAdapterFactory для MyResultObject:
public class MyResultObjectAdapterFactory implements TypeAdapterFactory {
@Override
@SuppressWarnings("unchecked")
public <T> TypeAdapter<T> create(Gson gson, TypeToken<T> type) {
if (type.getRawType()!= MyResultObject.class) return null;
TypeAdapter<MyResultObject> defaultAdapter = (TypeAdapter<MyResultObject>) gson.getDelegateAdapter(this, type);
return (TypeAdapter<T>) new MyResultObjectAdapter(defaultAdapter);
}
public class MyResultObjectAdapter extends TypeAdapter<MyResultObject> {
protected TypeAdapter<MyResultObject> defaultAdapter;
public MyResultObjectAdapter(TypeAdapter<MyResultObject> defaultAdapter) {
this.defaultAdapter = defaultAdapter;
}
@Override
public void write(JsonWriter out, MyResultObject value) throws IOException {
defaultAdapter.write(out, value);
}
@Override
public MyResultObject read(JsonReader in) throws IOException {
/*
This is the critical part. So if the value is a string,
Skip it (no exception) and return null.
*/
if (in.peek() == JsonToken.STRING) {
in.skipValue();
return null;
}
return defaultAdapter.read(in);
}
}
}
Наконец, зарегистрируйте MyResultObjectAdapterFactory для Gson:
Gson gson = new GsonBuilder()
.registerTypeAdapterFactory(new MyResultObjectAdapterFactory())
.create();
Теперь, при десериализации ApiResult json с этим объектом Gson, resultObj будет установлен null, если это строка.
Надеюсь, это решит вашу проблему =)
Ответ 2
У меня была аналогичная проблема, и в итоге я нашел следующее решение:
Вместо того, чтобы пытаться проанализировать ваш элемент в String
или Array
, попробуйте сохранить данные в простом java.lang.Object
Это предотвращает сбой синтаксического анализа или исключение.
например. с аннотациями GSON, свойство вашей модели будет выглядеть следующим образом:
@SerializedName("resultObj")
@Expose
private java.lang.Object resultObj;
Затем при доступе к вашим данным во время выполнения вы можете проверить, является ли ваше свойство resultObj
экземпляром String
или нет.
if(apiResultObject instanceof String ){
//Cast to string and do stuff
} else{
//Cast to array and do stuff
}
Оригинальное сообщение: fooobar.com/questions/550097/...
Ответ 3
Во-первых, это плохой дизайн API, с которым вы имеете дело.: - (
Вы можете использовать пользовательский JsonDeserializer для обработки этого случая.
Зарегистрируйте его с помощью "Дооснащения":
MyJsonDeserializer deserializer = new MyJsonDeserializer()).create();
final Gson gson = new GsonBuilder().registerTypeAdapter(ApiResult.class, deserializer);
RestAdapter restAdapter = new RestAdapter.Builder()
.setEndpoint(API_URL)
.setConverter(new GsonConverter(gson))
.build();
Ответ 4
Я повторно использую свое объяснение pojo.
В одном ответе он String
, а другой ответ - List<MajicalModel> magicalField
, поэтому один синтаксический анализ завершился неудачно.
Я меняю его на com.google.gson.JsonElement magicalField;
, он работает для меня. Таким образом, он анализирует raw json и также игнорирует несоответствие типов.
Ответ 5
вы также можете использовать этот код
ObjectMapper objectMapper = new ObjectMapper(); objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false); yourObject = objectMapper.readValue(jsonString,.class);