最近工作比较忙,很久没更新了,先向大家道个歉。
今天想分享一些工作中遇到的关于gson的坑,这么说其实不太准确,因为不能算是gson的坑,更多的是因为旧代码产生了一些不规范的数据导致使用gson时遇到了一些问题。
可能有的同学不了解gson,所以在分享坑之前先来介绍一下gson,已经熟练使用gson的同学可以直接跳到下一部分了。gson是Google开源的一个Java序列化库,它具有以下特点:
toJson()
和fromJson()
两个方法就可以实现Java对象和JSON字符串之间的序列化和反序列化那现在我们就来体验一下gson的第一个特性,使用简单。由团队中成员的能力参差不齐,所以一个简单易用性对这种基础组件是非常重要的。
在使用gson之前,我们需要添加依赖,我们的项目中使用的是Maven管理依赖,所以会在pom.xml文件中插入以下代码:
<dependency>
<groupId>com.google.code.gson</groupId>
<artifactId>gson</artifactId>
<version>2.8.6</version>
</dependency>
如果你的项目使用的是Gradle管理依赖,你需要新增下面的代码
dependencies {
implementation 'com.google.code.gson:gson:2.8.6'
}
依赖添加好以后,就可以直接开始使用了,这里我先来定义一个简单的POJO类(原谅我直接使用@Data)。
import lombok.Data;
@Data
public class User {
private String name;
private Integer age;
private String email;
private Boolean isVip;
}
接着就可以体验gson了,直接看一个demo吧。
public class GsonTest {
public static void main(String[] args) {
User user = new User();
user.setName("Jackey");
user.setAge(18);
user.setEmail("Jackeyzhe59@gmail.com");
user.setIsVip(true);
Gson gson = new Gson();
String json = gson.toJson(user);
System.out.println(json);
User u = gson.fromJson(json, User.class);
System.out.println(u.getName());
}
}
来看一下输出结果
完美!是不是非常简单?
那现在我们已经学会gson的基础用法了,接下来就进入正题,分享几个我在使用过程中遇到的实际问题以及解决方案。
在我们的使用过程中,遇到过这样的情况对于一个对象,在做序列化的时候,如果遇到了某个item为null,那么gson序列化出来的结果中就不会包含这个属性,这看起来很合理,不过对于我们的项目而言,前端同学需要根据有没有这个item来展示不同的信息,如果有这个item,但是值为空,那么前端就展示「不能告诉你」,如果没有这个item,前端同学就会展示为「没有这个item」。这么说可能令人有些费解,我们还是通过上面的例子来看。
{
"age":18,
"email":"Jackeyzhe59@gmail.com",
"isVip":true
}
如果我把name设置为null,那么序列化的结果就是这样。此时前端就会展示为「用户没有姓名信息」,如果我把name设置成空字符串,那么序列化结果就会不同。
{
"name":"",
"age":18,
"email":"Jackeyzhe59@gmail.com",
"isVip":true
}
这时我们的前端同学就会告诉用户,此用户不想展示名称。
我们现在想要避免出现第一种情况,虽然说可以约定不能把name设置为null,但是这种约定就很容易导致bug的产生,尤其是对于刚刚加入团队的新同学来说,他们可能会在不经意间就做了这样一个操作,在code review的时候也可能会出现遗漏。因此我选择定义一种TypeAdapter来约束我们序列化的工作。
这里可以先介绍一下gson中TypeAdapter的使用方法,TypeAdapter可以帮助我们自定义序列化/反序列化方式,它的使用也比较简单,首先我们需要定义一个自己的Adapter类,让它继承TypeAdapter类
import com.google.gson.TypeAdapter;
import com.google.gson.stream.JsonReader;
import com.google.gson.stream.JsonToken;
import com.google.gson.stream.JsonWriter;
import java.io.IOException;
public class StringNullAdapter extends TypeAdapter<String> {
@Override
public void write(JsonWriter out, String value) throws IOException {
if (value == null) { // 序列化使用的是adapter的write方法
out.value("");
return;
}
out.value(value);
}
@Override
public String read(JsonReader in) throws IOException {
if (in.peek() == JsonToken.NULL) { // 反序列化使用的是read方法
in.nextNull();
return "";
}
return in.nextString();
}
}
然后自己重写read和write方法,这里我们需要的是write方法。
其中参数value就是传入的对象属性,我们判断它是null,就将其转化为空字符串。
写好Adapter类之后,我们在新建gson的时候需要注册我们刚刚定义的Adapter。
Gson gson = new GsonBuilder()
.registerTypeAdapter(String.class, new StringNullAdapter())
.create();
GsonBuilder提供了registerTypeAdapter这个方法,可以直接为String类型都注册上我们自己的Adapter。
这时再将name设置为null,序列化结果就是我们期望的结果了。
我们在开发过程中还遇到了这样一个问题,在和另一个node写的服务做交互时,我们发现,node服务返回给我们的JSON对应的Boolean类型字段的值是0或1。
还用我们前面的例子来讲,如果node服务返回给我们的数据是这样的json字符串
{
"name":"Jackey",
"age":18,
"email":"Jackeyzhe59@gmail.com",
"isVip":1
}
那么我们在反序列化时就会报错
错误信息写的很清楚,我们的isVip字段是一个Boolean类型的,但是json中却是数字类型,gson没办法识别了。
这时我们可以让node服务来修改,也可以选择自己做适配。
自己做适配的话,有两种方式,一种是把isVip字段改成Number类型,但是由于isVip只可能存在两种值(是/否),用Number类型不是很合适。另一种方式就是再写一个Adapter来做适配,这次我们就需要重写read方法了。
public Boolean read(JsonReader in) throws IOException {
JsonToken peek = in.peek();
switch (peek) {
case BOOLEAN:
return in.nextBoolean();
case NULL:
in.nextNull();
return null;
case NUMBER:
return in.nextInt() != 0;
case STRING:
return Boolean.parseBoolean(in.nextString());
default:
throw new IllegalStateException("Expected BOOLEAN or NUMBER but was " + peek);
}
}
针对我们的问题,只需要处理NUMBER类型就可以了,不过这里我还兼容了STRING类型,把字符串的true/false转换成Boolean类型。你可以根据业务需要自行适配。例如有些团队可能会将null值认为是false,这里直接修改一下就好。
写好了Adapter以后还是别忘了注册。
细心的同学一定注意到了JsonToken这个类了,这是gson中对于Json符号类型的定义。它包含以下几种
BEGIN_ARRAY
END_ARRAY
BEGIN_OBJECT
END_OBJECT
NAME
STRING
NUMBER
BOOLEAN
NULL
END_DOCUMENT
从名称上就可以分辨出来BEGIN_ARRAY
和END_ARRAY
是对数组的标记,BEGIN_OBJECT
和END_OBJECT
是对对象的标记,NAME
标记的是json中的「key」,STRING
、NUMBER
、BOOLEAN
和NULL
都是json中值的类型,END_DOCUMENT
是json流结束的标识。
最后留一个问题大家可以和我一起讨论,我们在做反序列化时还遇到了BT的字符串的null,它本身所属的字段是Map类型,这样的Adapter应该怎么写呢?
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。