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);