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