Используйте Gson RuntimeTypeAdapterFactory с настройкой lenient = true - PullRequest
3 голосов
/ 15 января 2020

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 я заметил, что для опции lenient никогда не устанавливается значение true, потому что мы вызываем fromJsonTree метод com.google.gson.TypeAdapter, в котором мы создаем новый JsonTreeReader без возможности изменить параметр lenient.

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

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

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

1 Ответ

1 голос
/ 16 января 2020

Это не совсем ошибка в gson-extras, поскольку класс com.google.gson.TypeAdapter происходит из базовой библиотеки. Я бы предложил зарегистрировать еще один TypeAdapterFactory и принудительно lenient для всех 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();

См. Также:

...