Как обработать исключение NumberFormatException с помощью Gson при десериализации ответа JSON - PullRequest
43 голосов
/ 14 января 2012

Я читаю ответ JSON с Gson , который иногда возвращает NumberFormatException, потому что ожидаемое значение int установлено в пустую строку. Теперь мне интересно, как лучше всего справиться с таким исключением. Если значение является пустой строкой, десериализация должна быть 0.

Ожидаемый ответ JSON:

{
   "name" : "Test1",
   "runtime" : 90
}

Но иногда среда выполнения является пустой строкой:

{
   "name" : "Test2",
   "runtime" : ""
}

Класс Java выглядит следующим образом:

public class Foo
{
    private String name;
    private int runtime;
}

И десериализация такова:

String input = "{\n" +
               "   \"name\" : \"Test\",\n" +
               "   \"runtime\" : \"\"\n" +
               "}";

Gson gson = new Gson();
Foo foo = gson.fromJson(input, Foo.class);

Который выдает com.google.gson.JsonSyntaxException: java.lang.NumberFormatException: empty String, потому что вместо значения int возвращается пустая строка.

Есть ли способ сообщить Gson: ", если вы десериализуете поле runtime типа Foo и существует NumberFormatException, просто верните значение по умолчанию 0 "?

Мой обходной путь - использовать String в качестве поля типа runtime вместо int, но, возможно, есть лучший способ обработки таких ошибок.

Ответы [ 7 ]

30 голосов
/ 29 октября 2013

Вот пример, который я сделал для типа Long. Это лучший вариант:

    public class LongTypeAdapter extends TypeAdapter<Long>{
    @Override
    public Long read(JsonReader reader) throws IOException {
        if(reader.peek() == JsonToken.NULL){
            reader.nextNull();
            return null;
        }
        String stringValue = reader.nextString();
        try{
            Long value = Long.valueOf(stringValue);
            return value;
        }catch(NumberFormatException e){
            return null;
        }
    }
    @Override
    public void write(JsonWriter writer, Long value) throws IOException {
        if (value == null) {
            writer.nullValue();
            return;
        }
        writer.value(value);
    }
}

Вы можете обратиться к этой ссылке для получения дополнительной информации.

14 голосов
/ 15 января 2012

Сначала я попытался написать общий адаптер пользовательских типов для целочисленных значений, чтобы перехватить NumberFormatException и вернуть 0, но Gson не позволяет TypeAdaptors для примитивных типов:

java.lang.IllegalArgumentException: Cannot register type adapters for class java.lang.Integer

Послечто я ввел новый тип FooRuntime для поля runtime, поэтому класс Foo теперь выглядит следующим образом:

public class Foo
{
    private String name;
    private FooRuntime runtime;

    public int getRuntime()
    {
        return runtime.getValue();
    }
}

public class FooRuntime
{
    private int value;

    public FooRuntime(int runtime)
    {
        this.value = runtime;
    }

    public int getValue()
    {
        return value;
    }
}

Адаптер типа обрабатывает пользовательский процесс десериализации:

public class FooRuntimeTypeAdapter implements JsonDeserializer<FooRuntime>, JsonSerializer<FooRuntime>
{
    public FooRuntime deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException
    {
        int runtime;
        try
        {
            runtime = json.getAsInt();
        }
        catch (NumberFormatException e)
        {
            runtime = 0;
        }
        return new FooRuntime(runtime);
    }

    public JsonElement serialize(FooRuntime src, Type typeOfSrc, JsonSerializationContext context)
    {
        return new JsonPrimitive(src.getValue());
    }
}

Теперь необходимо использовать GsonBuilder для регистрации адаптера типа, поэтому пустая строка интерпретируется как 0, а не как NumberFormatException.

String input = "{\n" +
               "   \"name\" : \"Test\",\n" +
               "   \"runtime\" : \"\"\n" +
               "}";

GsonBuilder builder = new GsonBuilder();
builder.registerTypeAdapter(FooRuntime.class, new FooRuntimeTypeAdapter());
Gson gson = builder.create();
Foo foo = gson.fromJson(input, Foo.class);
10 голосов
/ 22 июля 2015

Быстрый и простой обходной путь - просто измените свое поле типа элемента времени выполнения на String и получите к нему доступ через геттер, который возвращает время выполнения как int:

public class Foo
{
    private String name;
    private String runtime;

    public int getRuntime(){
        if(runtime == null || runtime.equals("")){
            return 0;
        }
        return Integer.valueOf(trackId);
    }
}

=> нет необходимости десериализации json

6 голосов
/ 25 июля 2014

Я сделал этот TypeAdapter, который проверяет пустые строки и возвращает 0

public class IntegerTypeAdapter extends TypeAdapter<Number> {
@Override
public void write(JsonWriter jsonWriter, Number number) throws IOException {
    if (number == null) {
        jsonWriter.nullValue();
        return;
    }
    jsonWriter.value(number);
}

@Override
public Number read(JsonReader jsonReader) throws IOException {
    if (jsonReader.peek() == JsonToken.NULL) {
        jsonReader.nextNull();
        return null;
    }

    try {
        String value = jsonReader.nextString();
        if ("".equals(value)) {
            return 0;
        }
        return Integer.parseInt(value);
    } catch (NumberFormatException e) {
        throw new JsonSyntaxException(e);
    }
}

}

2 голосов
/ 14 января 2012

Это может помочь вам всегда принимать значение по умолчанию 0 для поля runtime в случае NumberFormatException, поскольку оно может быть единственным источником ошибки.

1 голос
/ 12 ноября 2018

Как указано в другом комментарии, начиная с GSON 2.3.1, вы можете регистрировать адаптеры типов для примитивных типов, здесь представлен адаптер типов, который обрабатывает типы int и Integer, и по умолчанию имеет значение 0 (или ноль) для строк, логических значений и обнуляет. Это продолжит анализ строк, в которых есть такие номера, как «время выполнения»: «5».

public static final TypeAdapter<Number> UNRELIABLE_INTEGER = new TypeAdapter<Number>() {
    @Override
    public Number read(JsonReader in) throws IOException {
        JsonToken jsonToken = in.peek();
        switch (jsonToken) {
            case NUMBER:
            case STRING:
                String s = in.nextString();
                try {
                    return Integer.parseInt(s);
                } catch (NumberFormatException ignored) {
                }
                try {
                    return (int)Double.parseDouble(s);
                } catch (NumberFormatException ignored) {
                }
                return null;
            case NULL:
                in.nextNull();
                return null;
            case BOOLEAN:
                in.nextBoolean();
                return null;
            default:
                throw new JsonSyntaxException("Expecting number, got: " + jsonToken);
        }
    }
    @Override
    public void write(JsonWriter out, Number value) throws IOException {
        out.value(value);
    }
};
public static final TypeAdapterFactory UNRELIABLE_INTEGER_FACTORY = TypeAdapters.newFactory(int.class, Integer.class, UNRELIABLE_INTEGER);

Вы можете зарегистрировать его с помощью следующего кода

Gson gson = new GsonBuilder()
            .registerTypeAdapterFactory(UNRELIABLE_INTEGER_FACTORY)
            .create();

Обратите внимание, что обычный JsonReader.nextInt (), который заменяет попытки вызова parseInt и parseDouble для токена, будет копировать внутреннюю логику для анализа целых чисел.

0 голосов
/ 13 марта 2019

Для тех, кто ищет ответ на этот вопрос для kotlin, сделайте то же самое, что и верхний ответ, но вместо того, чтобы делать

registerTypeAdapter(Long.class.java, adapter)

сделать

registerTypeAdapter(java.lang.Long::class.java, adapter)

...