web-dev-qa-db-fra.com

Comment gérer une NumberFormatException avec Gson dans la désérialisation d'une réponse JSON

Je lis une réponse JSON avec Gson , qui retourne parfois une NumberFormatException car une valeur attendue int est définie sur une chaîne vide. Maintenant, je me demande quelle est la meilleure façon de gérer ce genre d'exception. Si la valeur est une chaîne vide, la désérialisation doit être 0.

Réponse JSON attendue:

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

Mais parfois, le runtime est une chaîne vide:

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

La classe Java ressemble à ceci:

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

Et la désérialisation est la suivante:

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

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

Ce qui jette un com.google.gson.JsonSyntaxException: Java.lang.NumberFormatException: empty String car une chaîne vide est renvoyée au lieu d'une valeur int.

Existe-t-il un moyen de dire à Gson, " si vous désérialisez le champ runtime du type Foo et qu'il y a une exception NumberFormatException, renvoyez simplement la valeur par défaut 0 "?

Ma solution consiste à utiliser un String comme type du champ runtime au lieu de int, mais il existe peut-être une meilleure façon de gérer ces erreurs.

43
Soundlink

Au début, j'ai essayé d'écrire un adaptateur de type personnalisé général pour les valeurs entières, pour intercepter le NumberFormatException et retourner 0, mais Gson n'autorise pas les TypeAdaptors pour les types primitifs:

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

Après cela, j'ai introduit un nouveau type FooRuntime pour le champ runtime, donc la classe Foo ressemble maintenant à ceci:

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

Un adaptateur de type gère le processus de désérialisation personnalisé:

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

Maintenant, il est nécessaire d'utiliser GsonBuilder pour enregistrer l'adaptateur de type, donc une chaîne vide est interprétée comme 0 au lieu de lancer un 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);
14
Soundlink

Voici un exemple que j'ai fait pour le type Long. C'est une meilleure option:

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

Vous pouvez vous référer à ce lien pour en savoir plus.

30
Manish Khandelwal

Solution de contournement rapide et facile - Modifiez simplement le champ d'exécution de votre type de membre en String et accédez-y via getter qui renvoie runtime en tant qu'int:

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

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

=> pas de désérialisation json nécessaire

10
lukle

J'ai fait ce TypeAdapter qui vérifie les chaînes vides et retourne 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);
    }
}

}

6
Valdo Raya

Cela peut vous aider à toujours supposer une valeur par défaut de 0 pour le champ runtime en cas d'exception NumberFormatException, car il peut être la seule source d'erreur.

2
Milad Naseri

Comme indiqué dans un autre commentaire, à partir de GSON 2.3.1, vous pouvez enregistrer des adaptateurs de type pour les types primitifs, voici un adaptateur de type qui gère les types int et Integer, et par défaut gracieusement à 0 (ou null) pour les chaînes, les booléens et les null. Cela continuera à analyser les chaînes contenant des nombres comme "runtime": "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);

Vous pouvez l'enregistrer avec le code suivant

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

Notez que le JsonReader.nextInt () normal que cela remplace tente d'appeler parseInt et parseDouble sur le jeton, donc cela répliquera la logique interne pour l'analyse des entiers.

1
seanalltogether

Pour celui qui cherche une réponse à ce sujet pour kotlin, faites la même chose que la réponse du haut, mais au lieu de le faire

registerTypeAdapter(Long.class.Java, adapter)

faire

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

1
lucas_sales