在我做的项目中,服务器经常会用空字符串 “” 作为返回结果表示空值
但这在Gson当中就会遇到问题,如果这项数据的类型不是字符串,Gson解析就会报错
我们希望程序可以自动将空字符串解析为对应类型的空值,比如整型就解析为0,List型就解析为一个Empty List
这个问题可以说是我用Retrofit+Gson以来最大的一个坑,以至于我在研究时差不多都要把源码看完了
提一件离奇的事是,Gson在用整型解析空字符串时,报的居然是”Inavalid double”的错误
经过研究源码后发现,Gson会优先尝试解析为整型,解析失败并不会报错误,
继续尝试解析为double型,再失败才会报错,所以得到了”Inavalid double”
解决方案:
针对整型的解析,先写一个解析适配器,实现JsonSerializer
重写解析方法,先尝试用String类型解析,如果等于空字符串”“,则返回0值
否则再尝试用整型解析,并且catch数字格式异常转成Json解析异常抛出
public class IntegerDefault0Adapter implements JsonSerializer<Integer>, JsonDeserializer<Integer> {
@Override public Integer deserialize(JsonElement json, Type typeOfT,
JsonDeserializationContext context)
throws JsonParseException { try { if (json.getAsString().equals("")){ return 0;
}
} catch (Exception ignore){
} try { return json.getAsInt();
} catch (NumberFormatException e) { throw new JsonSyntaxException(e);
}
}
@Override public JsonElement serialize(Integer src, Type typeOfSrc, JsonSerializationContext context) { return new JsonPrimitive(src);
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
然后在GsonBuilder里注册适配器到类型Integer与类型int
public static Gson buildGson() { if (gson == null) {
gson = new GsonBuilder()
.setDateFormat("yyyy-MM-dd HH:mm:ss")
.registerTypeAdapter(Integer.class, new IntegerDefault0Adapter())
.registerTypeAdapter(int.class, new IntegerDefault0Adapter())
.create();
} return gson;
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
再在构建Retrofit时用这个自定义的Gson替换掉原生的
Retrofit = new Retrofit.Builder() .baseUrl(API_SERVER + "/")
//传入buildGson生成的自定义Gson .addConverterFactory(ResponseConverterFactory.create(buildGson())) .addCallAdapterFactory(RxJavaCallAdapterFactory.create()) .client(mOkHttpClient) .build();
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 1
- 2
- 3
- 4
- 5
- 6
- 7
这样Gson在遇到整型解析时可以将空字符串解析为0了
然后我打算用同样的方式解决List的解析问题,但没想到情况没有这么简单。
因为List并不像整形一样是一个基本类型,List本身在数据解析的时候是要带泛型的
我不可能在构建的时候就定好集合里的数据类型。
而如果不定泛型里的数据类型,重写适配器就得根据运行时遇到的类型分别进行操作,这无异于把Gson的工作重新做一遍。
并且经过研究源码后发现Gson对待List也并非当做一个类型去解析的
而是在初始化时带有一个CollectionTypeAdapterFactory,在遇到JsonArray类型的数据就会调用集合类型的解析器,然后再适配集合里的对应数据类型。总之一句话就是,挺复杂,并且不怎么能扩展。
经过研究后我找到的解决方案是
通过注解方式@JsonAdapter可以指定对应的适配器,优先级是高于默认的CollectionTypeAdapterFactory,和通过GsonBuilder传入的适配器的。
然后还是拷贝CollectionTypeAdapterFactory出来,改一份ListTypeAdapterFactory出来
/** * 列表解析类适配器的工厂类 * 必须通过注解@JsonAdapter方式才能优先于默认的CollectionTypeAdapterFactory */public final class ListTypeAdapterFactory implements TypeAdapterFactory { public TypeAdapter create(Gson gson, TypeToken typeToken) {
Type type = typeToken.getType();
Class super T> rawType = typeToken.getRawType(); if (!List.class.isAssignableFrom(rawType)) { return null;
}
Type elementType = $Gson$Types.getCollectionElementType(type, rawType);
TypeAdapter> elementTypeAdapter = gson.getAdapter(TypeToken.get(elementType));
@SuppressWarnings({"unchecked", "rawtypes"}) // create() doesn't define a type parameter
TypeAdapter result = new Adapter(gson, elementType, elementTypeAdapter); return result;
} private static final class Adapter extends TypeAdapter> { private final TypeAdapter elementTypeAdapter; public Adapter(Gson context, Type elementType,
TypeAdapter elementTypeAdapter) { this.elementTypeAdapter = new TypeAdapterRuntimeTypeWrapper(
context, elementTypeAdapter, elementType);
} //关键部分是这里,重写解析方法
public List read(JsonReader in) throws IOException { //null值返回null
if (in.peek() == JsonToken.NULL) { in.nextNull(); return null;
} //新建一个空的列表
List list = new ArrayList<>(); try { in.beginArray(); while (in.hasNext()) {
E instance = elementTypeAdapter.read(in);
list.add(instance);
} in.endArray(); //正常解析成为列表
} catch (IllegalStateException e){ //如果是空字符串,会有BEGIN_ARRAY报错
//此时尝试解析成字符串,如果不是空字符串,则依旧抛出异常
//如果是空字符串,则不抛出异常,使最终返回一个空的列表
if (!"".equals(in.nextString())){ throw e;
}
} return list;
} public void write(JsonWriter out, List list) throws IOException { if (list == null) { out.nullValue(); return;
} out.beginArray(); for (E element : list) {
elementTypeAdapter.write(out, element);
} out.endArray();
}
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 53
- 54
- 55
- 56
- 57
- 58
- 59
- 60
- 61
- 62
- 63
- 64
- 65
- 66
- 67
- 68
- 69
- 70
- 71
- 72
- 73
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 53
- 54
- 55
- 56
- 57
- 58
- 59
- 60
- 61
- 62
- 63
- 64
- 65
- 66
- 67
- 68
- 69
- 70
- 71
- 72
- 73
最后就是Model类里面注解指定
public class UserListModel{
@JsonAdapter(ListTypeAdapterFactory.class)
List users;
}
- 1
- 2
- 3
- 4
- 1
- 2
- 3
- 4
这样Gson就能将空字符串解析成为空列表了。