Используйте Gson RuntimeTypeAdapterFactory с настройкой lenient = true

API, который мы принимаем, возвращает некоторый искаженный JSON (в частности, Double.NaN сериализуется в String с «NaN»). Чтобы разрешить это, gson всегда устанавливает значение lenient в true и корректно сериализует его обратно в Double.NaN .

Теперь мы хотели использовать класс RuntimeTypeAdapterFactory из gson-extras для удаления некоторого стандартного кода. Только что описанное поведение здесь больше не применимо, мы получаем сообщение

java.lang.NumberFormatException: JSON запрещает NaN и бесконечности: NaN

который должен был быть проигнорирован из-за lenient опции, обычно устанавливаемой из gson.

Рассмотрим этот небольшой пример:

class Base
{
   String type;
   double malformedField;
}

class A extends Base{}
class B extends Base{}

Мы используем поле type , чтобы определить, какой подкласс мы хотим, потому что, конечно, в противном случае мы не могли бы вывести правильный тип.

Это настройка, которую мы используем для создания наших подклассов и десериализации:

RuntimeTypeAdapterFactory<Base> adapterFactory = RuntimeTypeAdapterFactory.of(Base.class, "type", true)
    .registerSubtype(A.class, "A")
    .registerSubtype(B.class, "B");

Gson gson = new GsonBuilder()
    .registerTypeAdapterFactory(adapterFactory)
    //.setLenient() // Has no effect at all
    .create();

String json = "{"type":"A", "malformedField": "NaN"}";
Base a = gson.fromJson(json, Base.class); // throws said Exception

Реализация в RuntimeTypeAdapterFactory :

После проверки кода из RuntimeTypeAdapterFactory я заметил, что для параметра RuntimeTypeAdapterFactory никогда не устанавливается значение true, потому что мы fromJsonTree метод fromJsonTree для com.google.gson.TypeAdapter , в котором мы создаем новый JsonTreeReader без возможности изменения параметра JsonTreeReader .

public final T fromJsonTree(JsonElement jsonTree) {
    try {
        JsonReader jsonReader = new JsonTreeReader(jsonTree);
        return read(jsonReader);
    } catch (IOException e) {
        throw new JsonIOException(e);
    }
}

Я попытался вручную установить для этого значения при отладке значение true и это, казалось, работало как ожидалось (исключение NumberFormatException и объекта не было правильно десериализовано с указанным типом).

Теперь вопрос: есть ли что-то, чего мне не хватает, почему мы не можем использовать здесь lenient вариант? На первый взгляд это похоже на ошибку gson-extras, но я не уверен.

Всего 1 ответ


Это не совсем ошибка в gson-extras так как класс com.google.gson.TypeAdapter происходит из базовой библиотеки. Я бы предложил зарегистрировать еще одну TypeAdapterFactory и принудительно JsonReader все экземпляры JsonReader :

class AlwaysLenientTypeAdapterFactory implements TypeAdapterFactory {

    public final <T> TypeAdapter<T> create(Gson gson, TypeToken<T> type) {
        final TypeAdapter<T> delegate = gson.getDelegateAdapter(this, type);
        return createCustomTypeAdapter(delegate);
    }

    private <T> TypeAdapter<T> createCustomTypeAdapter(TypeAdapter<T> delegate) {
        return new TypeAdapter<T>() {
            @Override
            public void write(JsonWriter out, T value) throws IOException {
                delegate.write(out, value);
            }

            @Override
            public T read(JsonReader in) throws IOException {
                in.setLenient(true);
                return delegate.read(in);
            }
        };
    }
}

Использование:

Gson gson = new GsonBuilder()
        .registerTypeAdapterFactory(adapterFactory)
        .registerTypeAdapterFactory(new AlwaysLenientTypeAdapterFactory())
        .create();

Смотрите также:


Есть идеи?

10000