前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >如何更好的使用Gson

如何更好的使用Gson

作者头像
Jackeyzhe
发布2020-06-18 15:17:16
1.1K0
发布2020-06-18 15:17:16
举报
文章被收录于专栏:代码洁癖患者代码洁癖患者

最近工作比较忙,很久没更新了,先向大家道个歉。

今天想分享一些工作中遇到的关于gson的坑,这么说其实不太准确,因为不能算是gson的坑,更多的是因为旧代码产生了一些不规范的数据导致使用gson时遇到了一些问题。

gson简介

可能有的同学不了解gson,所以在分享坑之前先来介绍一下gson,已经熟练使用gson的同学可以直接跳到下一部分了。gson是Google开源的一个Java序列化库,它具有以下特点:

  • 使用简单,只需要掌握toJson()fromJson()两个方法就可以实现Java对象和JSON字符串之间的序列化和反序列化
  • 允许将现有的不可修改的对象与JSON互相转换
  • 对Java的泛型支持的很好
  • 允许自定义一些对象的表现形式
  • 支持复杂对象的序列化
使用gson

那现在我们就来体验一下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的基础用法了,接下来就进入正题,分享几个我在使用过程中遇到的实际问题以及解决方案。

案例分享

null转为空字符串

在我们的使用过程中,遇到过这样的情况对于一个对象,在做序列化的时候,如果遇到了某个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,序列化结果就是我们期望的结果了。

数字和Boolean到底用哪个

我们在开发过程中还遇到了这样一个问题,在和另一个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_ARRAYEND_ARRAY是对数组的标记,BEGIN_OBJECTEND_OBJECT是对对象的标记,NAME标记的是json中的「key」,STRINGNUMBERBOOLEANNULL都是json中值的类型,END_DOCUMENT是json流结束的标识。

讨论

最后留一个问题大家可以和我一起讨论,我们在做反序列化时还遇到了BT的字符串的null,它本身所属的字段是Map类型,这样的Adapter应该怎么写呢?

本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2020-06-15,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 代码洁癖患者 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • gson简介
    • 使用gson
    • 案例分享
      • null转为空字符串
        • 数字和Boolean到底用哪个
        • 扩展一点
        • 讨论
        相关产品与服务
        文件存储
        文件存储(Cloud File Storage,CFS)为您提供安全可靠、可扩展的共享文件存储服务。文件存储可与腾讯云服务器、容器服务、批量计算等服务搭配使用,为多个计算节点提供容量和性能可弹性扩展的高性能共享存储。腾讯云文件存储的管理界面简单、易使用,可实现对现有应用的无缝集成;按实际用量付费,为您节约成本,简化 IT 运维工作。
        领券
        问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档