专栏首页我要变牛人生苦短,我用Gson

人生苦短,我用Gson

Life is short, you need Gson

一、JSON简介

JSON(JavaScript Object Notation, JS 对象标记) 是一种轻量级的数据交换格式。它基于 ECMAScript (w3c制定的js规范)的一个子集,采用完全独立于编程语言的文本格式来存储和表示数据。简洁和清晰的层次结构使得 JSON 成为理想的数据交换语言。 易于人阅读和编写,同时也易于机器解析和生成,并有效地提升网络传输效率。 JSON 键值对是用来保存JS对象的一种方式,和JS对象的写法也大同小异,键/值对组合中的键名写在前面并用双引号 “” 包裹,使用冒号 : 分隔,然后紧接着值,如下例子所示:

{“firstName”: “John”}

二、谷歌Gson

解析和生成json的方式很多,java的有Jackson、Gson、FastJson等,Gson是谷歌提供的一款开源解析和生成json的库。

1、Gson实例化方法

Gson gson = new Gson(); Gson gson = new GsonBuilder().create();

第二种初始化方法,可以选择更多的序列化与反序列化方式,下面会详细介绍。

2、Gson基本用法

gson主要提供了fromJson和toJson两个方法,fromJson用于反序列化,toJson用于把json序列化为Json字符串。

public <T> T fromJson(String json, Class<T> classOfT) throws JsonSyntaxException {

}
public String toJson(Object src) {

}

fromJson()第二个入参是反序列化成的对象类型

3、简单对象与Json的转换

class Person{
    private String name;
    private  int age;

    public Person(String name, int age) {
        this.name = name;
        this.age = age;
    }

    @Override
    public String toString() {
        return "Person{" +
                "name='" + name + '\'' +
                ", age=" + age +
                '}';
    }
}


public static void main(String[] args) {
        Person person = new Person("steven", 18);
        Gson gson = new Gson();
        String json = gson.toJson(person);
        System.out.println(json);
        String personJson = "{\"name\":\"steven\",\"age\":18}";
        Person person1 = gson.fromJson(personJson, Person.class);
        System.out.println(person1);
}
输出--》
{"name":"steven","age":18}
Person{name='steven', age=18}

可以看出Gson的强悍之处,普通的类库序列化和反序列时必须要求所对应的类中属性必须含有setter和getter方法,Gson完全不需要。

4、Map和Json的转换

public static void main(String[] args) {
        Person person = new Person("steven", 18);
        Map<String, Person> personMap = new HashMap<>();
        personMap.put("person", person);
        Gson gson = new Gson();
        String json = gson.toJson(personMap);
        System.out.println(json);
        String personJson = "{\"person\":{\"name\":\"steven\",\"age\":18} }";
        Map map = gson.fromJson(personJson, Map.class);
        System.out.println(map);
    }
输出--》
{"person":{"name":"steven","age":18} }
{person={name=steven, age=18.0} }

此处可以看出通过gson可以近乎完美的转换map和json,可以看出有个有小问题fromJson时,数字类型的value转换时会转成double类型,会把18转成18.0,下文会有解决方案。

三、Gson注解

1、序列化名注解@SerializedName

@SerializedName("personName")
private String name;

public static void main(String[] args) {
    Person person = new Person("steven", 18);
    Gson gson = new Gson();
    String json = gson.toJson(person);
    System.out.println(json);
}
输出--》
{"personName":"steven","age":18}

2、暴露序列化注解@Expose

使用此注解时就可以选择性的序列化类的属性,前面介绍的方法都是直接使用new Gson(),toJson()和fromJson()方法,这会将全部的字段序列化或反序列化,但实际中,有时我们并不需要全部字段序列化。或者随着项目的发展,版本可能会升级,某些实体类里可能会新增几个字段,这时版本不同数据不同,即低版本不能解析新的json数据(因为新的数据还有新增的字段)等。将对象序列化,默认情况下@Expose注解是不起作用的,需要用GsonBuilder创建Gson的时候调用了GsonBuilder.excludeFieldsWithoutExposeAnnotation()方法。

加上注解等同于: @Expose(serialize = true,deserialize = true) 不加注解等同于: @Expose(serialize = false,deserialize = false)

@Expose(serialize = false,deserialize = true)
private String name;
@Expose(serialize = true,deserialize = true)
private int age;

public static void main(String[] args) {
     Person person = new Person("steven", 18);
     Gson gson = new GsonBuilder().excludeFieldsWithoutExposeAnnotation().create();
     System.out.println(json);
     String personJson = "{\"name\":\"steven\",\"age\":18}";
     Person person1 = gson.fromJson(personJson, Person.class);
    System.out.println(person1);
}
输出--》
{"age":18}
Person{name='steven', age=18}

3、版本序列化注解@Since和@Util

使用注解表示哪些字段是哪个版本的,@Since(1.0)代表1.0版本,应用版本比它高或同等时会被序列化,反之不会,也可以用@Until(1.0)

@Since(2.0)
private String name;
@Since(1.0)
private int age;


public static void main(String[] args) {
        Person person = new Person("steven", 18);
        Gson gson = new GsonBuilder().setVersion(1.0).create();
        String json = gson.toJson(person);
        System.out.println(json);
        String personJson = "{\"name\":\"steven\",\"age\":18}";
        Person person1 = gson.fromJson(personJson, Person.class);
        System.out.println(person1);
}

输出--》
{"age":18}
Person{name='null', age=18}

四、Gson高阶用法

1、泛型类反序列化

fromJson时使用TypeToken格式

public static void main(String[] args) {
        Gson gson = new Gson();
        String json = "{\"person\":{\"name\":\"steven\",\"age\":18} }";
        Map<String, Person> personMap = gson.fromJson(json, new TypeToken<Map<String, Person>>() {
        }.getType());
        System.out.println(personMap);
}

2、Map复杂类型的key

使用new GsonBuilder().enableComplexMapKeySerialization().create();

//不加enableComplexMapKeySerialization时会默认调用key的toString()方法,而不是转Json
public static void main(String[] args) {
        Gson gson = new GsonBuilder().create();
        Map<Object, Object> map = new HashMap<>();
        Person person = new Person("steven", 17);
        map.put(person,"steven");
        String json = gson.toJson(map);
        System.out.println(json);
}
输出--》
{"Person{name\u003d\u0027steven\u0027, age\u003d17}":"steven"}


//加上之后:
public static void main(String[] args) {
        Gson gson = new GsonBuilder().enableComplexMapKeySerialization().create();
        Map<Object, Object> map = new HashMap<>();
        Person person = new Person("steven", 17);
        map.put(person,"steven");
        String json = gson.toJson(map);
        System.out.println(json);
}
输出--》
[[{"name":"steven","age":17},"steven"]]

3、特殊字符的处理

使用disableHtmlEscaping接触特殊字符编码问题

//不加disableHtmlEscaping时,等号,引号都会转码:
public static void main(String[] args) {
        Gson gson = new GsonBuilder().disableHtmlEscaping().create();
        Map<Object, Object> map = new HashMap<>();
        map.put("moi","subnetwork=1500,meid=3200");
        String json = gson.toJson(map);
        System.out.println(json);
}
输出--》
{"moi":"subnetwork\u003d1500,meid\u003d3200"}

//加上disableHtmlEscaping时:
public static void main(String[] args) {
        Gson gson = new GsonBuilder().disableHtmlEscaping().create();
        Map<Object, Object> map = new HashMap<>();
        map.put("moi","subnetwork=1500,meid=3200");
        String json = gson.toJson(map);
        System.out.println(json);
 }
 输出--》
{"moi":"subnetwork=1500,meid=3200"}

4、NULL值处理

Gson默认不会转换为null的属性,使用serializeNulls时不会丢失null属性

//不加serializeNulls会丢弃掉null值的属性:
 public static void main(String[] args) {
        Gson gson = new GsonBuilder().create();
        Map<Object, Object> map = new HashMap<>();
        map.put("moi", "1500");
        map.put("name", null);
        String json = gson.toJson(map);
        System.out.println(json);
}
输出--》
{"moi":"1500"}

//加上serializeNulls:
public static void main(String[] args) {
        Gson gson = new GsonBuilder().serializeNulls().create();
        Map<Object, Object> map = new HashMap<>();
        map.put("moi", "1500");
        map.put("name", null);
        String json = gson.toJson(map);
        System.out.println(json);
}
输出--》
{"moi":"1500","name":null}

5、数字类型处理

如前面所提到的一点,数字类型转换时,会出现精度问题,如下所示:

public static void main(String[] args) {
        Gson gson = new GsonBuilder().serializeNulls().create();
        String json = "{\"moi\":\"1500\",\"age\":18}";
        Map<Object, Object> map = gson.fromJson(json,new TypeToken<Map<Object, Object>>(){}.getType());
        System.out.println(map);
}

输出--》
{moi=1500, age=18.0}

Gson根据待解析的类型定位到具体的TypeAdaptor 类,其接口的主要方法如下:

public abstract class TypeAdapter<T> {

  /**
   * Writes one JSON value (an array, object, string, number, boolean or null)
   * for {@code value}.
   *
   * @param value the Java object to write. May be null.
   */
  public abstract void write(JsonWriter out, T value) throws IOException;



  /**
   * Reads one JSON value (an array, object, string, number, boolean or null)
   * and converts it to a Java object. Returns the converted object.
   *
   * @return the converted Java object. May be null.
   */
  public abstract T read(JsonReader in) throws IOException;
}

通过read方法从JsonReader中读取相应的数据组装成最终的对象,由于Map中的字段的声明类型是Object,最终Gson会定位到内置的ObjectTypeAdaptor类,我们来分析一下该类的逻辑过程。

public final class ObjectTypeAdapter extends TypeAdapter<Object> {
  public static final TypeAdapterFactory FACTORY = new TypeAdapterFactory() {
    @SuppressWarnings("unchecked")
    @Override public <T> TypeAdapter<T> create(Gson gson, TypeToken<T> type) {
      if (type.getRawType() == Object.class) {
        return (TypeAdapter<T>) new ObjectTypeAdapter(gson);
      }
      return null;
    }
  };

  private final Gson gson;

  ObjectTypeAdapter(Gson gson) {
    this.gson = gson;
  }

  @Override public Object read(JsonReader in) throws IOException {
    JsonToken token = in.peek();
    switch (token) {
    case BEGIN_ARRAY:
      List<Object> list = new ArrayList<Object>();
      in.beginArray();
      while (in.hasNext()) {
        list.add(read(in));
      }
      in.endArray();
      return list;

    case BEGIN_OBJECT:
      Map<String, Object> map = new LinkedTreeMap<String, Object>();
      in.beginObject();
      while (in.hasNext()) {
        map.put(in.nextName(), read(in));
      }
      in.endObject();
      return map;

    case STRING:
      return in.nextString();
      //此处可以看出,所有数值类型都转换为了Double类型
    case NUMBER:
      return in.nextDouble();

    case BOOLEAN:
      return in.nextBoolean();

    case NULL:
      in.nextNull();
      return null;

    default:
      throw new IllegalStateException();
    }
  }

  @SuppressWarnings("unchecked")
  @Override public void write(JsonWriter out, Object value) throws IOException {
    if (value == null) {
      out.nullValue();
      return;
    }

    TypeAdapter<Object> typeAdapter = (TypeAdapter<Object>) gson.getAdapter(value.getClass());
    if (typeAdapter instanceof ObjectTypeAdapter) {
      out.beginObject();
      out.endObject();
      return;
    }

    typeAdapter.write(out, value);
  }
}

看到该逻辑过程,如果Json对应的是Object类型,最终会解析为Map 类型;其中Object类型跟Json中具体的值有关,比如双引号的””值翻译为STRING。可以看到数值类型(NUMBER)全部转换为了Double类型,所以就有了之前的问题,整型数据被转换为了Double类型,比如18变为了18.0。 所以想在不改变源码的基础上,实现数值类型的正确转换,需要新增一个适配器。

public class MyTypeAdapter extends TypeAdapter<Object> {

    @Override
    public Object read(JsonReader in) throws IOException {
        JsonToken token = in.peek();
        switch (token) {
            case BEGIN_ARRAY:
                List<Object> list = new ArrayList<Object>();
                in.beginArray();
                while (in.hasNext()) {
                    list.add(read(in));
                }
                in.endArray();
                return list;

            case BEGIN_OBJECT:
                Map<String, Object> map = new HashMap<String, Object>();
                in.beginObject();
                while (in.hasNext()) {
                    map.put(in.nextName(), read(in));
                }
                in.endObject();
                return map;

            case STRING:
                return in.nextString();

            case NUMBER:
                //将其作为一个字符串读取出来
                String numberStr = in.nextString();
                //返回的numberStr不会为null
                if (numberStr.contains(".") || numberStr.contains("e")
                        || numberStr.contains("E")) {
                    return Double.parseDouble(numberStr);
                }
                return Long.parseLong(numberStr);
            case BOOLEAN:
                return in.nextBoolean();

            case NULL:
                in.nextNull();
                return null;

            default:
                throw new IllegalStateException();
        }
    }

    @Override
    public void write(JsonWriter out, Object value) throws IOException {
        // 序列化无需实现
    }
}

此时就可以实现浮点型数值按double类型转换,整数型按Long型转换。另外一点可以看出当类型为BEGIN_OBJECT时ObjectTypeAdapter返回的Gson自定义的map类型LinkedTreeMap,如果使用时用到强转为HashMap会报错,由于我们使用的都是HashMap,所以自定义SonTypeAdapter复写成了HashMap。

五、总结

Gson是Google公司发布的一个开放源代码的Java库,主要用途为序列化Java对象为JSON字符串,或反序列化JSON字符串成Java对象。Gson核心jar包不到1M,非常精简,但提供的功能无疑是非常强大的,如果使用JDK自带的JSON解析API,使用起来相对比较繁琐一点,而且代码量较多,推荐大家可以尝试使用。

本文分享自微信公众号 - 你呀不牛(notNiu),作者:不牛

原文出处及转载信息见文内详细说明,如有侵权,请联系 yunjia_community@tencent.com 删除。

原始发表时间:2021-05-24

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

我来说两句

0 条评论
登录 后参与评论

相关文章

  • 人生苦短,我用python

    基础书籍推荐 父与子的编程之旅 这本书说的都是一些基本的概念,告诉我们编程到底是怎么一回事,初步了解Python的世界。 笨方法学Python 前面很适合新手学...

    企鹅号小编
  • 人生苦短,我用Python

    据 IEEE Spectrum 发布的研究报告显示,2017年,Python成为最受欢迎的编程语言,而2016年,Python也是高居第三,可见Python受众...

    诸葛青云
  • 人生苦短,我用python

    python的火热是大家有目共睹的,在生物信息领域也不例外,近几年新开发的软件中python的出现频率越来越高,学习和掌握python, 是大势所趋。

    生信修炼手册
  • 人生苦短,我用PyCharm

    本文经机器之心(微信公众号:almosthuman2014)授权转载,禁止二次转载。

    zhangqibot
  • 人生苦短,我用PyCharm

    机器之心之前也没系统地介绍过 PyCharm,怎样配置环境、怎样 DeBug、怎样同步 GitHub 等等可能都是通过经验或者摸索学会的。在本文中,我们并不会提...

    机器之心
  • 人生苦短,我用PyCharm

    机器之心之前也没系统地介绍过 PyCharm,怎样配置环境、怎样 DeBug、怎样同步 GitHub 等等可能都是通过经验或者摸索学会的。在本文中,我们并不会提...

    小小詹同学
  • 人生苦短,我用PyCharm

    本文假设读者熟悉 Python 开发,且计算机中已安装某个版本的 Python。该教程将使用 Python 3.6 版本,屏幕截图和 demo 均来自 macO...

    Python进击者
  • 人生苦短,我用VIM!

    熟悉我的录友,应该都知道我是vim流,无论是写代码还是写文档(Markdown),都是vim,都没用IDE。

    代码随想录
  • 人生苦短,我用Python(1)

    在文章的开头给大家介绍一下Python语言,作者Guido von Rossum。对,就是图中的“人生苦短我用Python”那哥们。你可能以为我会用很多文字介绍...

    Se7eN_HOU
  • 人生苦短我用python: eval,e

    01、函数原型 eval(expression[, globals[, locals]])

    py3study
  • 人生苦短,我学python

    py3study
  • 人生苦短,我用Python | Python简史

    2017年,根据 IEEE Spectrum 发布的研究报告显示,Python成为世界上最受欢迎的语言,C 和 Java 分别位居第二和第三位。

    嘉为蓝鲸
  • Python崛起:“人生苦短,我用Pyt

      这些年,编程语言的发展进程很快,在商业公司、开源社区两股力量的共同推动下,涌现出诸如Go、Swift这类后起之秀,其中最为耀眼的是Python。

    py3study
  • 人生苦短,我要换Go!

    在使用健壮的软件开发编程语言方面,Golang 已经被许多人选中。这种编程语言为程序员提供了独特的好处。

    新智元
  • 人生苦短,为什么我要用Python?

    本教程的目的是让你相信两件事:首先,Python 是一种非常棒的编程语言;其次,如果你是一名科学家,Python 很可能值得你去学习。本教程并非想要说明 Pyt...

    机器之心
  • 人生苦短,为什么我要用Python?

    本教程的目的是让你相信两件事:首先,Python 是一种非常棒的编程语言;其次,如果你是一名科学家,Python 很可能值得你去学习。本教程并非想要说明 Pyt...

    CDA数据分析师
  • 人生苦短,为什么我要用Python?

    导读:随着机器学习的兴起,Python 逐步成为了「最受欢迎」的语言。它简单易用、逻辑明确并拥有海量的扩展包,因此其不仅成为机器学习与数据科学的首选语言,同时在...

    华章科技
  • 人生苦短,我用Python之小游戏

    虽然Python可能被粗略地分类为“脚本语言”(script language),但实际上一些大规模软件开发计划例如Zope、Mnet及BitTorrent,G...

    小Bob来啦
  • 人生苦短,我用k8s--------------k8s的前世今生

    Kubernetes的名字来自希腊语,意思是“舵手” 或 “领航员”。K8s是将8个字母“ubernete”替换为“8”的缩写。

    不吃小白菜

扫码关注云+社区

领取腾讯云代金券