当前项目解析json用的工具是google的gson,原因嘛,因为有GsonFormat插件,可以直接把服务端传回的json字符串转成Bean对象。不过在实际使用中出现了以下两个问题:

  1. 传回的字符串或者数组为null,使用时若不加空指针判断,容易出现空指针异常。
  2. 测试用的数值为0,结果用GsonFomat生成的对象默认为int类型,但可能该字段的真实类型为float,所以之后收到类型为float的数据时,就可能导致解析出错。

针对上述问题,都可以通过自定义TypeAdapter解决。

阅读过Gson的源码后发现,Gson的数据解析都是委托到各个TypeAdapter内进行处理的。在Gson的构造函数内会预先加载一部分TypeAdapter,包含String、int、long、double等类型,都存放在factories中,如下:

Gson(final Excluder excluder, final FieldNamingStrategy fieldNamingPolicy,
final Map<Type, InstanceCreator<?>> instanceCreators, boolean serializeNulls,
boolean complexMapKeySerialization, boolean generateNonExecutableGson, boolean htmlSafe,
boolean prettyPrinting, boolean serializeSpecialFloatingPointValues,
LongSerializationPolicy longSerializationPolicy,
List<TypeAdapterFactory> typeAdapterFactories) {
this.constructorConstructor = new ConstructorConstructor(instanceCreators);
this.serializeNulls = serializeNulls;
this.generateNonExecutableJson = generateNonExecutableGson;
this.htmlSafe = htmlSafe;
this.prettyPrinting = prettyPrinting; List<TypeAdapterFactory> factories = new ArrayList<TypeAdapterFactory>(); // built-in type adapters that cannot be overridden
factories.add(TypeAdapters.JSON_ELEMENT_FACTORY);
factories.add(ObjectTypeAdapter.FACTORY); // the excluder must precede all adapters that handle user-defined types
factories.add(excluder); // user's type adapters
factories.addAll(typeAdapterFactories); // 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);
factories.add(TypeAdapters.SHORT_FACTORY);
factories.add(TypeAdapters.newFactory(long.class, Long.class,
longAdapter(longSerializationPolicy)));
factories.add(TypeAdapters.newFactory(double.class, Double.class,
doubleAdapter(serializeSpecialFloatingPointValues)));
factories.add(TypeAdapters.newFactory(float.class, Float.class,
floatAdapter(serializeSpecialFloatingPointValues)));
factories.add(TypeAdapters.NUMBER_FACTORY);
factories.add(TypeAdapters.CHARACTER_FACTORY);
factories.add(TypeAdapters.STRING_BUILDER_FACTORY);
factories.add(TypeAdapters.STRING_BUFFER_FACTORY);
factories.add(TypeAdapters.newFactory(BigDecimal.class, TypeAdapters.BIG_DECIMAL));
factories.add(TypeAdapters.newFactory(BigInteger.class, TypeAdapters.BIG_INTEGER));
factories.add(TypeAdapters.URL_FACTORY);
factories.add(TypeAdapters.URI_FACTORY);
factories.add(TypeAdapters.UUID_FACTORY);
factories.add(TypeAdapters.LOCALE_FACTORY);
factories.add(TypeAdapters.INET_ADDRESS_FACTORY);
factories.add(TypeAdapters.BIT_SET_FACTORY);
factories.add(DateTypeAdapter.FACTORY);
factories.add(TypeAdapters.CALENDAR_FACTORY);
factories.add(TimeTypeAdapter.FACTORY);
factories.add(SqlDateTypeAdapter.FACTORY);
factories.add(TypeAdapters.TIMESTAMP_FACTORY);
factories.add(ArrayTypeAdapter.FACTORY);
factories.add(TypeAdapters.ENUM_FACTORY);
factories.add(TypeAdapters.CLASS_FACTORY); // type adapters for composite and user-defined types
factories.add(new CollectionTypeAdapterFactory(constructorConstructor));
factories.add(new MapTypeAdapterFactory(constructorConstructor, complexMapKeySerialization));
factories.add(new ReflectiveTypeAdapterFactory(
constructorConstructor, fieldNamingPolicy, excluder)); this.factories = Collections.unmodifiableList(factories);
}

我们可以自定义TypeAdapter,将其放入facotries中,并且gson在解析json时使用对应的TypeAdapter来的,而我们手动添加的TypeAdapter会优先于预设的TypeAdapter被使用。有兴趣的可以看看源码,还是比较简单的。

为了解决问题1,我们先定义一个StringAdapter,代码如下:

 /**
* 自定义TypeAdapter ,null对象将被解析成空字符串
*/
public static final TypeAdapter<String> STRING = new TypeAdapter<String>() {
public String read(JsonReader reader) {
try {
if (reader.peek() == JsonToken.NULL) {
reader.nextNull();
return "";//原先是返回Null,这里改为返回空字符串
}
return reader.nextString();
} catch (Exception e) {
e.printStackTrace();
}
return "";
} public void write(JsonWriter writer, String value) {
try {
if (value == null) {
writer.nullValue();
return;
}
writer.value(value);
} catch (Exception e) {
e.printStackTrace();
}
}
};

这样在读取到null节点时,就自动变成返回空字符串了。

接下去定义一个Int类型的TypeAdapter:

/**
* 自定义adapter,解决由于数据类型为Int,实际传过来的值为Float,导致解析出错的问题
* 目前的解决方案为将所有Int类型当成Double解析,再强制转换为Int
*/
public static final TypeAdapter<Number> INTEGER = new TypeAdapter<Number>() {
@Override
public Number read(JsonReader in) throws IOException {
if (in.peek() == JsonToken.NULL) {
in.nextNull();
return ;
}
try {
double i = in.nextDouble();//当成double来读取
return (int) i;//强制转为int
} catch (NumberFormatException e) {
throw new JsonSyntaxException(e);
}
} @Override
public void write(JsonWriter out, Number value) throws IOException {
out.value(value);
}
};

数组部分略麻烦,由于gson用以数组解析的Adapter是不可重写的,只好拷贝出来,重新写了个类,如下

import com.google.gson.Gson;
import com.google.gson.TypeAdapter;
import com.google.gson.TypeAdapterFactory;
import com.google.gson.internal.$Gson$Types;
import com.google.gson.internal.ConstructorConstructor;
import com.google.gson.internal.ObjectConstructor;
import com.google.gson.reflect.TypeToken;
import com.google.gson.stream.JsonReader;
import com.google.gson.stream.JsonToken;
import com.google.gson.stream.JsonWriter; import java.io.IOException;
import java.lang.reflect.Type;
import java.util.Collection; /**
* 自定义CollectionTypeAdapterFactory,使json内的数组为null时,返回空数组而不是null对象
*/
public final class CollectionTypeAdapterFactory implements TypeAdapterFactory {
private final ConstructorConstructor constructorConstructor; public CollectionTypeAdapterFactory(ConstructorConstructor constructorConstructor) {
this.constructorConstructor = constructorConstructor;
} public <T> TypeAdapter<T> create(Gson gson, TypeToken<T> typeToken) {
Type type = typeToken.getType(); Class<? super T> rawType = typeToken.getRawType();
if (!Collection.class.isAssignableFrom(rawType)) {
return null;
} Type elementType = $Gson$Types.getCollectionElementType(type, rawType);
TypeAdapter<?> elementTypeAdapter = gson.getAdapter(TypeToken.get(elementType));
ObjectConstructor<T> constructor = constructorConstructor.get(typeToken); @SuppressWarnings({"unchecked", "rawtypes"}) // create() doesn't define a type parameter
TypeAdapter<T> result = new Adapter(gson, elementType, elementTypeAdapter, constructor);
return result;
} private static final class Adapter<E> extends TypeAdapter<Collection<E>> {
private final TypeAdapter<E> elementTypeAdapter;
private final ObjectConstructor<? extends Collection<E>> constructor; public Adapter(Gson context, Type elementType,
TypeAdapter<E> elementTypeAdapter,
ObjectConstructor<? extends Collection<E>> constructor) {
this.elementTypeAdapter =
new TypeAdapterRuntimeTypeWrapper<E>(context, elementTypeAdapter, elementType);
this.constructor = constructor;
} public Collection<E> read(JsonReader in) throws IOException {
if (in.peek() == JsonToken.NULL) {
in.nextNull();
//这里做了修改,原先是返回null,改为返回空数组
return constructor.construct();
} Collection<E> collection = constructor.construct();
in.beginArray();
while (in.hasNext()) {
E instance = elementTypeAdapter.read(in);
collection.add(instance);
}
in.endArray();
return collection;
} public void write(JsonWriter out, Collection<E> collection) throws IOException {
if (collection == null) {
out.nullValue();
return;
} out.beginArray();
for (E element : collection) {
elementTypeAdapter.write(out, element);
}
out.endArray();
}
}
}

注意上面的TypeAdapterRuntimeTypeWrapper类不是public的,所以也得拷贝出来写一个到本地。

接下去是使用部分:

import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.google.gson.InstanceCreator;
import com.google.gson.JsonElement;
import com.google.gson.JsonSerializationContext;
import com.google.gson.JsonSerializer;
import com.google.gson.JsonSyntaxException;
import com.google.gson.TypeAdapter;
import com.google.gson.TypeAdapterFactory;
import com.google.gson.internal.ConstructorConstructor;
import com.google.gson.reflect.TypeToken;
import com.google.gson.stream.JsonReader;
import com.google.gson.stream.JsonToken;
import com.google.gson.stream.JsonWriter; import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject; import java.io.IOException;
import java.lang.reflect.Field;
import java.lang.reflect.Type;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Objects; /**
* Created by linjizong on 15/7/20.
*/
public class GsonUtils { public static Gson gson; /**
* 自定义TypeAdapter ,null对象将被解析成空字符串
*/
public static final TypeAdapter<String> STRING = new TypeAdapter<String>() {
public String read(JsonReader reader) {
try {
if (reader.peek() == JsonToken.NULL) {
reader.nextNull();
return "";//原先是返回Null,这里改为返回空字符串
}
return reader.nextString();
} catch (Exception e) {
e.printStackTrace();
}
return "";
} public void write(JsonWriter writer, String value) {
try {
if (value == null) {
writer.nullValue();
return;
}
writer.value(value);
} catch (Exception e) {
e.printStackTrace();
}
}
}; /**
* 自定义adapter,解决由于数据类型为Int,实际传过来的值为Float,导致解析出错的问题
* 目前的解决方案为将所有Int类型当成Double解析,再强制转换为Int
*/
public static final TypeAdapter<Number> INTEGER = new TypeAdapter<Number>() {
@Override
public Number read(JsonReader in) throws IOException {
if (in.peek() == JsonToken.NULL) {
in.nextNull();
return ;
}
try {
double i = in.nextDouble();
return (int) i;
} catch (NumberFormatException e) {
throw new JsonSyntaxException(e);
}
} @Override
public void write(JsonWriter out, Number value) throws IOException {
out.value(value);
}
}; static {
GsonBuilder gsonBulder = new GsonBuilder();
gsonBulder.registerTypeAdapter(String.class, STRING); //所有String类型null替换为字符串“”
gsonBulder.registerTypeAdapter(int.class, INTEGER); //int类型对float做兼容 //通过反射获取instanceCreators属性
try {
Class builder = (Class) gsonBulder.getClass();
Field f = builder.getDeclaredField("instanceCreators");
f.setAccessible(true);
Map<Type, InstanceCreator<?>> val = (Map<Type, InstanceCreator<?>>) f.get(gsonBulder);//得到此属性的值
//注册数组的处理器
gsonBulder.registerTypeAdapterFactory(new CollectionTypeAdapterFactory(new ConstructorConstructor(val)));
} catch (NoSuchFieldException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} gson = gsonBulder.create();
} /**
* Json字符串 转为指定对象
*
* @param json json字符串
* @param type 对象类型
* @param <T> 对象类型
* @return
* @throws JsonSyntaxException
*/
public static <T> T toBean(String json, Class<T> type) throws JsonSyntaxException {
T obj = gson.fromJson(json, type);
return obj;
} }

通过GsonBuilder的registerTypeAdapter方法可以直接注册TypeAdapter。而CollectionTypeAdapterFactory方法需要使用到GsonBuidler的instanceCreators字段,只好通过反射来获取了。接下去只要使用GsonUtils.toBean()就行了。

Gson:自定义TypeAdapter的更多相关文章

  1. gson中TypeAdapter实现自定义序列化操作

    最近在项目中遇到这么一个问题,我们后台需要向前端返回一个 json 数据,就是将一个地理位置对象以json的格式返回到前台,但是这个地理位置对象中的经纬度是Double数据类型,项目中规定,如果经纬度 ...

  2. gson 自定义对象转换格式

    有时候我们希望gson按照我们想要的方式转换,比如将日期转换为时间戳 class GsonBuilderUtil { public static Gson create() { GsonBuilder ...

  3. 重新认识一个强大的 Gson

    从一个 Bug 说起 不知道你们发现没有,你写完的程序无论当时怎么测试,过一段时间总会出 Bug .再说一个每天都在发生的例子:在你写完一篇博客后,立即检查的话,总是查不出自己写的错别字. 据说这些都 ...

  4. Gson的学习与使用

    Gson介绍: GSON是Google提供的用来在Java对象和JSON数据之间进行映射的Java类库.可以将一个Json字符转成一个Java对象,或者将一个Java转化为Json字符串. 特点: a ...

  5. Gson序列化对象如何忽略字段

    Gson序列化对象如何忽略字段 Gson版本 2.8.2 梗概 用注解@Expose(serialize = false, deserialize = false)在类的成员上以告诉Gson 跳过本字 ...

  6. Gson 解析教程

    Gson 是google解析Json的一个开源框架,同类的框架fastJson,JackJson等等 本人fastJson用了两年,也是从去年才开始接触Gson,希望下面的总结会对博友有用,至于Gso ...

  7. Gson 是google解析Json的一个开源框架,同类的框架fastJson,JackJson

    Gson 是google解析Json的一个开源框架,同类的框架fastJson,JackJson等等 本人fastJson用了两年,也是从去年才开始接触Gson,希望下面的总结会对博友有用,至于Gso ...

  8. 除了FastJson,你还有选择: Gson简易指南

    前言 这个周末被几个技术博主的同一篇公众号文章 fastjson又被发现漏洞,这次危害可导致服务瘫痪! 刷屏,离之前漏洞事件没多久,fastjson 又出现严重 Bug.目前项目中不少使用了 fast ...

  9. [已开源/文章教程]独立开发 一个社交 APP 的源码/架构分享 (已上架)

    0x00 背景 真不是和被推荐了2天的博客园一位大神较真,从他那篇文章的索引式文章内容也学习到了很多东西,看评论区那么多对社交APP源码有兴趣的,正巧我上周把我的一个社交APP开源了,包括androi ...

随机推荐

  1. Windows 64位系统下安装JAVA环境

    文件名称:jdk-7u51-windows-x64 下载Windows 64位平台的JDK安装文件. 安装 1.双击打开下载好的文件,点击“下一步”开始安装. 2. ①默认选择安装全部功能,不做调整. ...

  2. HEVC简介】CTU、CU、PU、TU结构

    https://www.cnblogs.com/DwyaneTalk/p/5711342.html

  3. [置顶] 记最近一次Nodejs全栈开发经历

    背景: 前段时间大部门下新成立了一个推广百度OCR.文字识别.图像识别等科技能力在金融领域应用的子部门.因为部门刚成立,基础设施和人力都是欠缺的.当时分到我们部门的任务是抽调一个人做新部门主站前端开发 ...

  4. 21天学通C++_Day6

    0.指针&数组 数组是指向其第一个元素的指针,即数组变量就是指针.故可将(*)用于数组,也可将([])用于指针,eg: int MyNums[5] = {0}; int* pNums = My ...

  5. 编程技巧:使用整数同时进行多个true|false判断

    情景 : 假设需要判断某银行用户的其中一个账号(profileA),币种(Currency)为人民币(CNY),余额是否大于1,0000,然后进行某业务逻辑处理. 概述: 为了进行这种判断,需要判断/ ...

  6. C的动态链表建立

    运用到的函数为: 动态内存分配函数malloc()              比如:char *name=(char *)malloc(20);  相当与c++的new关键字 动态内存释放函数free ...

  7. linux下vi命令(转)

    进入vi的命令 vi filename :打开或新建文件,并将光标置于第一行首 vi +n filename :打开文件,并将光标置于第n行首 vi + filename :打开文件,并将光标置于最后 ...

  8. HDU4612Warm up 边双连通 Tarjan缩点

    N planets are connected by M bidirectional channels that allow instant transportation. It's always p ...

  9. Ubuntu下环境变量设置

    [内容来自网络] 相应配置文件介绍: 1) /etc/profile :在登录时,操作系统定制用户环境使用的第一个文件,此文件为系统的每个用户设置环境信息,当用户第一次登录时,改文件被执行 2) /e ...

  10. ofdm理解(转载)

    说明:以下文字,灰色为吹水文,黑色为正文,蓝色为采用实际应用中的参数所作的说明. 起因是这样的.时间回到07年底,4G方兴之时,同桌隔壁的隔壁"小白"同学说看不太明白OFDMA的原 ...