当前项目解析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. Python3 flask nginx uwsgi 环境搭建

    配置项目的时候一般使用虚拟环境,是各个项目的环境独立起来,更多方便管理.至于如何使用搜索即可,并不难 1.安装python3 yum -y install zlib-devel bzip2-devel ...

  2. Spring核心概念(一)

    1.解决JavaEE的轻量级框架. 2.环境搭建 第一步:导入spring的jar包 第二步:导入配置文件(一般写在resource下面) 第三步:创建一个类(bean) 第四步:在主配置文件中注入这 ...

  3. (转)MapReduce Design Patterns(chapter 4 (part 1))(七)

    Chapter 4. Data Organization Patterns 与前面章节的过滤器相比,本章是关于数据重组.个别记录的价值通常靠分区,分片,排序成倍增加.特别是在分布式系统中,因为这能提高 ...

  4. Java 进阶巩固:什么是注解以及运行时注解的使用

    这篇文章 2016年12月13日星期二 就写完了,当时想着等写完另外一篇关于自定义注解的一起发.结果没想到这一等就是半年多 - -. 有时候的确是这样啊,总想着等条件更好了再干,等准备完全了再开始,结 ...

  5. Jira简单使用操作指引20150605

    1.选择项目 2.点击[问题]——>[所有问题] 3.选择状态(一般开发关注[新增.处理中],测试关注[已解决.已作废]) 4.选择[more],勾选[解决版本].[影响版本].[解决人],我们 ...

  6. 产生num个不重复的随机数组

    createDiffRandom : function (from,to,num) { // 产生num个不重复的随机数组 var arr=[],json={}; // 随机数数组 , 标记json对 ...

  7. ng-if 判断条件中不能 使用变量名字拼接,switch可以

  8. nodejs返回接口给前端

    1.修改app.js文件,将其中的user路由去掉. 2.在index路由中配置如下:   router.all('*', function(req, res, next) { res.header( ...

  9. 在IIS上搭建FTP站点

    操作环境 系统:win7 IIS版本:7.5 FTP传输工具:FlashXP 概述 本文介绍了如何在win7下利用IIS(默认已安装IIS和FTP功能)搭建FTP站点,FTP站点的常用配置. 快速搭建 ...

  10. JAVA多线程----用--取钱问题1

    “生产者-消费者”模型,也就是生产者线程只负责生产,消费者线程只负责消费,在消费者发现无内容可消费时则睡觉.下面举一个比较实际的例子——生活费问题. 生 活费问题是这样的:学生每月都需要生活费,家长一 ...