1. Json解析的场景和解决方案
1.1 场景
1 先上一个栗子(不含null字段的解析)
val jsonString = "{\"name\":\"woochen\",\"obj\":{\"address\":\"china\"},\"list\":[{\"address\":\"china\"}]}"
data class TestBean(
val name:String="",
val obj:InnerBean=InnerBean(),
val list:List<InnerBean> = listOf()
){
data class InnerBean(
val address:String=""
)
}
注:实体类中字段赋初始值只在json字段没被解析时生效
val result = gson.fromJson<TestBean>(jsonString, TestBean::class.java)
println("解析结果:${result}")
结果:
解析结果:TestBean(name=woochen, obj=InnerBean(address=china), list=[InnerBean(address=china)])
2 再来一个栗子(含null字段的数据解析) 如果服务端没有特别处理,我们可能会接收到这样的json
val jsonString = "{\"name\":null,\"obj\":null,\"list\":null}"
我们现在还是用原来的实体类来解析,结果是这样的
TestBean(name=null, obj=null, list=null)
看似没毛病,我们接着执行这样的代码
result.name.length
编译可以通过,执行的时候会抛出异常
java.lang.NullPointerException
聪明的小伙伴可能会说,这样写不就好了
result.name?.length
结果却是然并卵。。。
由于Gson是通过使用类的无参构造反射生成实例的,所以成功完成了解析过程,但是kotlin的null检验就导致了使用时的空指针问题。
1.2 解决方案
解决方案一: 让服务端给所以字段都赋上初始值,避免null的出现
如果你可以使用这种方案,本文对你毫无用处,不建议继续阅读。
解决方案二: 使用可空类型的实体类解析
data class TestBean(
val name:String?="",
val obj:InnerBean?=InnerBean(),
val list:List<InnerBean>? = listOf()
){
data class InnerBean(
val address:String?=""
)
}
这样解析后,我们在使用字段的时候,在作为非空参数使用时都要加上?或者!!,个人觉得还是有些麻烦的
解决方案三: 直接在解析过程中将null值进行重新赋值,第三部分将提供具体实现方案
//实体类
data class TestBean(
val name:String="",
val obj:InnerBean=InnerBean(),
val list:List<InnerBean> = listOf()
){
data class InnerBean(
val address:String=""
)
}
//目标json
val jsonString = "{\"name\":null,\"obj\":null,\"list\":null}"
//解析结果
TestBean(name=, obj=InnerBean(address=), list=[])
2. gson源码分析
为了实现方案二,我们先进行简单的源码分析,看看Gson是如何将一个json字符串转为具体的实体类。只需要具体代码实现的请跳过该部分,直接看第三部分
以下只包含核心代码部分,源码部分是java代码,项目代码均为kotlin,阅读起来有任何不适请见谅(ps:用了kotlin实在不想写java代码)。
val result = gson.fromJson<TestBean>(jsonString, TestBean::class.java)
public <T> T fromJson(String json, Class<T> classOfT) throws JsonSyntaxException {
//1.获取Class的类型
Object object = fromJson(json, (Type) classOfT);
return Primitives.wrap(classOfT).cast(object);
}
public <T> T fromJson(String json, Type typeOfT) throws JsonSyntaxException {
if (json == null) {
return null;
}
//2.根据传入的字符串数据得到Reader对象(真正进行读写操作的对象)
StringReader reader = new StringReader(json);
T target = (T) fromJson(reader, typeOfT);
return target;
}
public <T> T fromJson(Reader json, Type typeOfT) throws JsonIOException, JsonSyntaxException {
JsonReader jsonReader = newJsonReader(json);
//3.包装Reader对象,用来解析{,null,[等字符
T object = (T) fromJson(jsonReader, typeOfT);
assertFullConsumption(object, jsonReader);
return object;
}
public <T> T fromJson(JsonReader reader, Type typeOfT) throws JsonIOException, JsonSyntaxException {
...
reader.peek();
isEmpty = false;
TypeToken<T> typeToken = (TypeToken<T>) TypeToken.get(typeOfT);
//4.根据类型获得相应的适配器
TypeAdapter<T> typeAdapter = getAdapter(typeToken);
//5.借助适配器和Reader对象对json进行解析并返回指定的对象
T object = typeAdapter.read(reader);
return object;
...
}
从上面的代码可以看出步骤4是比较关键的一步,它决定了类型的转化方式
public <T> TypeAdapter<T> getAdapter(TypeToken<T> type) {
...
for (TypeAdapterFactory factory : factories) {
//1.遍历factories,如果找到匹配的adpter就直接返回
TypeAdapter<T> candidate = factory.create(this, type);
if (candidate != null) {
call.setDelegate(candidate);
typeTokenCache.put(type, candidate);
return candidate;
}
}
...
}
factories是在Gson的构造中传入的,gson开放了api用来设置factory
//GsonBuilder.java
public GsonBuilder registerTypeAdapterFactory(TypeAdapterFactory factory) {
factories.add(factory);
return this;
}
public Gson create() {
List<TypeAdapterFactory> factories = new ArrayList<TypeAdapterFactory>(this.factories.size() + this.hierarchyFactories.size() + 3);
factories.addAll(this.factories);
//我们添加的factory被翻转了
Collections.reverse(factories);
List<TypeAdapterFactory> hierarchyFactories = new ArrayList<TypeAdapterFactory>(this.hierarchyFactories);
Collections.reverse(hierarchyFactories);
factories.addAll(hierarchyFactories);
addTypeAdaptersForDate(datePattern, dateStyle, timeStyle, factories);
return new Gson(excluder, fieldNamingPolicy, instanceCreators,
serializeNulls, complexMapKeySerialization,
generateNonExecutableJson, escapeHtmlChars, prettyPrinting, lenient,
serializeSpecialFloatingPointValues, longSerializationPolicy,
datePattern, dateStyle, timeStyle,
this.factories, this.hierarchyFactories, factories);
}
//Gson.Java
Gson(final Excluder excluder, final FieldNamingStrategy fieldNamingStrategy,
final Map<Type, InstanceCreator<?>> instanceCreators, boolean serializeNulls,
boolean complexMapKeySerialization, boolean generateNonExecutableGson, boolean htmlSafe,
boolean prettyPrinting, boolean lenient, boolean serializeSpecialFloatingPointValues,
LongSerializationPolicy longSerializationPolicy, String datePattern, int dateStyle,
int timeStyle, List<TypeAdapterFactory> builderFactories,
List<TypeAdapterFactory> builderHierarchyFactories,
List<TypeAdapterFactory> factoriesToBeAdded) {
List<TypeAdapterFactory> factories = new ArrayList<TypeAdapterFactory>();
// built-in type adapters that cannot be overridden
factories.add(TypeAdapters.JSON_ELEMENT_FACTORY);
factories.add(ObjectTypeAdapter.FACTORY);
factories.add(excluder);
//!!!这里就是我们自己添加的factory
factories.addAll(factoriesToBeAdded);
// type adapters for basic platform types
factories.add(TypeAdapters.STRING_FACTORY);
factories.add(TypeAdapters.INTEGER_FACTORY);
factories.add(TypeAdapters.BOOLEAN_FACTORY);
factories.add(TypeAdapters.BYTE_FACTORY);
...
}
factoriesToBeAdded是通过registerTypeAdapterFactory设置的factory,它们会添加到Gson默认的适配器的前面,而factories是从头开始遍历来匹配adpter的,所以我们可以通过覆盖指定类型的适配器,来走我们自己的adapter的逻辑从而实现需求。
于是我们找其中一个默认adapter来查看解析逻辑,以下是默认对字符串的解析适配器TypeAdapters.STRING_FACTORY
//TypeAdapters.STRING_FACTORY
public static final TypeAdapter<String> STRING = new TypeAdapter<String>() {
@Override
public String read(JsonReader in) throws IOException {
JsonToken peek = in.peek();
if (peek == JsonToken.NULL) {
in.nextNull();
//!!!当字段值为null时,返回null
return null;
}
/* coerce booleans to strings for backwards compatibility */
if (peek == JsonToken.BOOLEAN) {
return Boolean.toString(in.nextBoolean());
}
return in.nextString();
}
@Override
public void write(JsonWriter out, String value) throws IOException {
out.value(value);
}
};
比如:我们想要字符串为null时,解析后返回“”,而不是null,于是就可以这样写
class StringNullAdapter : TypeAdapter<String>() {
@Throws(IOException::class)
override fun read(reader: JsonReader): String {
if (reader.peek() === JsonToken.NULL) {
reader.nextNull()
//只修改了这里
return ""
}
return reader.nextString()
}
@Throws(IOException::class)
override fun write(writer: JsonWriter, value: String?) {
if (value == null) {
writer.nullValue()
return
}
writer.value(value)
}
}
3. Gson非空TypeAdapter实现
//NotNullAdapterFactory.kt
class NotNullAdapterFactory : TypeAdapterFactory {
override fun <T> create(gson: Gson, type: TypeToken<T>): TypeAdapter<T>? {
val rawType = type.rawType as Class<T>
return if (rawType == String::class.java) {
StringNullAdapter() as TypeAdapter<T>
} else if (rawType == Int::class.java || rawType == Integer::class.java) {
IntNullAdapter() as TypeAdapter<T>
}else if (rawType == Boolean::class.java) {
BooleanNullAdapter() as TypeAdapter<T>
}else if (rawType == Long::class.java) {
LongNullAdapter() as TypeAdapter<T>
}else {
null
}
}
class StringNullAdapter : TypeAdapter<String>() {
@Throws(IOException::class)
override fun read(reader: JsonReader): String {
if (reader.peek() === JsonToken.NULL) {
reader.nextNull()
return ""
}
return reader.nextString()
}
@Throws(IOException::class)
override fun write(writer: JsonWriter, value: String?) {
if (value == null) {
writer.nullValue()
return
}
writer.value(value)
}
}
class IntNullAdapter : TypeAdapter<Int>() {
@Throws(IOException::class)
override fun read(reader: JsonReader): Int {
if (reader.peek() === JsonToken.NULL) {
reader.nextNull()
return 0
}
return reader.nextInt()
}
@Throws(IOException::class)
override fun write(writer: JsonWriter, value: Int?) {
if (value == null) {
writer.nullValue()
return
}
writer.value(value)
}
}
class BooleanNullAdapter : TypeAdapter<Boolean>() {
@Throws(IOException::class)
override fun read(reader: JsonReader): Boolean {
if (reader.peek() === JsonToken.NULL) {
reader.nextNull()
return false
}
return reader.nextBoolean()
}
@Throws(IOException::class)
override fun write(writer: JsonWriter, value: Boolean?) {
if (value == null) {
writer.nullValue()
return
}
writer.value(value)
}
}
class LongNullAdapter : TypeAdapter<Long>() {
@Throws(IOException::class)
override fun read(reader: JsonReader): Long {
if (reader.peek() === JsonToken.NULL) {
reader.nextNull()
return 0
}
return reader.nextLong()
}
@Throws(IOException::class)
override fun write(writer: JsonWriter, value: Long?) {
if (value == null) {
writer.nullValue()
return
}
writer.value(value)
}
}
}
其中,对象类型和数组类型比较特殊,可以将默认的ReflectiveTypeAdapterFactory和CollectionTypeAdapterFactory相关的文件拷贝出来进行修改。修改方法于上面相同
最后使用是这样的
val gsonBulder = GsonBuilder()
val f: Field = gsonBulder::class.java.getDeclaredField("instanceCreators")
f.isAccessible = true
val instanceCreator = f.get(gsonBulder) as Map<Type, InstanceCreator<*>>
val constructorConstructor = ConstructorConstructor(instanceCreator)
//注意拦截顺序,上面的源码中看到了,通过registerTypeAdapterFactory添加的factories会被进行反转,所以最后添加的会被最先遍历。
gsonBulder.registerTypeAdapterFactory(ReflectiveTypeAdapterFactory(constructorConstructor, FieldNamingPolicy.IDENTITY, Excluder.DEFAULT,JsonAdapterAnnotationTypeAdapterFactory(constructorConstructor)))
.registerTypeAdapterFactory(CollectionTypeAdapterFactory(constructorConstructor))
.registerTypeAdapterFactory(NotNullAdapterFactory())
val gson = gsonBulder.create()
注意:
- 以上代码,笔者在项目中亲测可用。
- CollectionTypeAdapterFactory和ReflectiveTypeAdapterFactory相关的代码过长且比较简单,就没有贴出来,按照NotNullAdapterFactory中的adpter的方式修改即可。
- NotNullAdapterFactory并没有覆盖所有的默认类型,只是笔者项目中使用到的。
- 如果使用了ReflectiveTypeAdapterFactory会拦截默认对象的解析,它只适用于自定义的对象解析,自动装箱的Integer和Long等是无法正常解析的,如果待解析的gson中出现了,需要在NotNullAdapterFactory中进行判断,可以参考NotNullAdapterFactory中的代码。
大功告成,再贴一遍方案二
//实体类
data class TestBean(
val name:String="",
val obj:InnerBean=InnerBean(),
val list:List<InnerBean> = listOf()
){
data class InnerBean(
val address:String=""
)
}
//目标json
val jsonString = "{\"name\":null,\"obj\":null,\"list\":null}"
//解析结果
TestBean(name=, obj=InnerBean(address=), list=[])
本人水平有限,如有错误请留言指正。如果本文对你有一点点帮助,请随手点个赞鼓励一下吧