Java Json API:Gson序列化

澳门新葡亰3522平台游戏 1

Java Json
API:Gson使用简单入门

gson
github地址google/gson

本篇文章是基于Gson官方使用指导(Gson User
Guide)以及Gson解析的优秀外文(来自http://www.javacreed.com/
)做出的一个翻译和归纳。
博客原链接:
Gson全解析(上)-Gson基础
Gson全解析(中)-TypeAdapter的使用
Gson全解析(下)-Gson性能分析

通过调用 Gson API 可以把 Java 对象转换为 JSON
格式的字符串(项目主页)。在这篇文章中,我们将会讲到如何通过
Gson 默认实现和自定义实现方式,将 Java  对象转换为 JSON 字符串。


对于那些不熟悉 Gson 的读者,建议在读本篇文章之前读一下这两篇文章:简单
Gson 实例和 Gson
反序列化实例。另外,这篇文章的讲述方式和Gson反序列化实例一样,并且使用了相同的例子。

前言

最近在研究Retrofit中使用的Gson的时候,发现对Gson的一些深层次的概念和使用比较模糊,所以这里做一个知识点的归纳整理。

Gson(又称Google
Gson)是Google公司发布的一个开放源代码的Java库,主要用途为序列化Java对象为JSON字符串,或反序列化JSON字符串成Java对象。而JSON(JavaScript
Object Notation)
是一种轻量级的数据交换格式,易于人阅读和编写,同时也易于机器解析和生成,广泛应用于各种数据的交互中,尤其是服务器与客户端的交互。


注意

基本概念

  • Serialization:序列化,使Java对象到Json字符串的过程。
  • Deserialization:反序列化,字符串转换成Java对象。
  • JSON数据中的JsonElement有下面这四种类型:
    JsonPrimitive —— 例如一个字符串或整型
    JsonObject—— 一个以 JsonElement 名字(类型为
    String)作为索引的集合。也就是说可以把 JsonObject 看作值为
    JsonElement 的键值对集合。
    JsonArray—— JsonElement
    的集合。注意数组的元素可以是四种类型中的任意一种,或者混合类型都支持。
    JsonNull—— 值为null

请注意,在文章中我们将互换格式化或序列化的术语。

Gson解决的问题

  1. 提供一种像toString()和构造方法的很简单的机制,来实现Java
    对象和Json之间的互相转换。

  2. 允许已经存在的无法改变的对象,转换成Json,或者Json转换成已存在的对象。

  3. 允许自定义对象的表现形式

  4. 支持任意的复杂对象

  5. 能够生成可压缩和可读的Json的字符串输出。


下面列出的所有代码都可以在这里找到: http://java-creed-examples.googlecode.com/svn/gson/Gson
Serialiser
Example/ 。大多数例子不会包含完整的代码,可能会忽略和要讨论的例子不相关的片段。读者可以从上面的链接下载查看完整的代码。

Gson处理对象的几个重要点

1 推荐把成员变量都声明称private的

2 没有必要用注解(@Expose
注解)指明某个字段是否会被序列化或者反序列化,所有包含在当前类(包括父类)中的字段都应该默认被序列化或者反序列化

3 如果某个字段被 transient
这个Java关键词修饰,就不会被序列化或者反序列化

4 下面的实现方式能够正确的处理null
1)当序列化的时候,如果对象的某个字段为null,是不会输出到Json字符串中的。
2)当反序列化的时候,某个字段在Json字符串中找不到对应的值,就会被赋值为null

5 如果一个字段是 synthetic
的,他会被忽视,也即是不应该被序列化或者反序列化

6 内部类(或者anonymous class(匿名类),或者local
class(局部类,可以理解为在方法内部声明的类))的某个字段和外部类的某个字段一样的话,就会被忽视,不会被序列化或者反序列化


简单的例子

考虑下面这个 Java 对象。

package com.javacreed.examples.gson.part1;

public class Book {

  private String[] authors;
  private String isbn10;
  private String isbn13;
  private String title;

  // Methods removed for brevity

}

这个简单的 Java 类封装了一本书的属性。假如我们需要将其序列化为下面这个
JSON 对象。

{
  "title": "Java Puzzlers: Traps, Pitfalls, and Corner Cases",
  "isbn-10": "032133678X",
  "isbn-13": "978-0321336781",
  "authors": [
    "Joshua Bloch",
    "Neal Gafter"
  ]
}

Gson 不需要任何特殊配置就可以序列化 Book 类。Gson 使用 Java 字段名称作为
JSON 字段的名称,并赋予对应的值。如果仔细地看一下上面的那个 JSON
示例会发现, ISBN 字段包含一个减号:isbn-10 和
isbn-13。不幸的是,使用默认配置不能将这些字段包含进来。解决问题的办法之一就是使用注解,就像在这篇文章中描述的那样:Gson
注解示例。使用注解可以自定义
JSON
字段的名称,Gson将会以注解为准进行序列化。另一个方法就是使用 JsonSerialiser (Java
Doc),如下所示:

package com.javacreed.examples.gson.part1;

import java.lang.reflect.Type;

import com.google.gson.JsonArray;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.google.gson.JsonPrimitive;
import com.google.gson.JsonSerializationContext;
import com.google.gson.JsonSerializer;

public class BookSerialiser implements JsonSerializer {

    @Override
    public JsonElement serialize(final Book book, final Type typeOfSrc, final JsonSerializationContext context) {
        final JsonObject jsonObject = new JsonObject();
        //The serialisation code is missing

        return jsonObject;
    }
}

上面的例子还缺失了重要部分,需要通过补充序列化代码来完善。在添加更多代码使其变得复杂之前,我们先来理解下这个类。

JsonSerializer
接口要求类型是将要进行序列化的对象类型。在这个例子中,我们要序列化的
Java 对象是 Book。serialize()方法的返回类型必须是一个 JsonElement
(Java
文档)类型的实例。详见这篇文章:Gson
反序列化实例,下面列出了JsonElement
四种具体实现类型:

  • JsonPrimitive (Java
    Doc) ——
    例如一个字符串或整型
  • JsonObject (Java
    Doc) ——
    一个以 JsonElement 名字(类型为 String)作为索引的集合。类似于
    Map<String,JsonElement>集合(Java
    Doc)
  • JsonArray (Java
    Doc)——
    JsonElement
    的集合。注意数组的元素可以是四种类型中的任意一种,或者混合类型都支持。
  • JsonNull (Java
    Doc) ——
    值为null

JsonElement的类型

澳门新葡亰3522平台游戏 1

上面这张图片展示了 JsonElement 的所有类型。可以把 JsonObject 看作值为
JsonElement 的键值对集合。因此,这些值可以是另外四种对象。

下面是序列化的完整实例:

package com.javacreed.examples.gson.part1;

import java.lang.reflect.Type;

import com.google.gson.JsonArray;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.google.gson.JsonPrimitive;
import com.google.gson.JsonSerializationContext;
import com.google.gson.JsonSerializer;

public class BookSerialiser implements JsonSerializer {

    @Override
    public JsonElement serialize(final Book book, final Type typeOfSrc, final JsonSerializationContext context) {
        final JsonObject jsonObject = new JsonObject();
        jsonObject.addProperty("title", book.getTitle());
        jsonObject.addProperty("isbn-10", book.getIsbn10());
        jsonObject.addProperty("isbn-13", book.getIsbn13());

        final JsonArray jsonAuthorsArray = new JsonArray();
        for (final String author : book.getAuthors()) {
            final JsonPrimitive jsonAuthor = new JsonPrimitive(author);
            jsonAuthorsArray.add(jsonAuthor);
        }
        jsonObject.add("authors", jsonAuthorsArray);

        return jsonObject;
    }
}

我们在这里添加了一些代码。在理解整个图片的含义前,我们先把它拆成一个个小的部分,先来解释下每部分的含义。

如果要序列化这个 Java 对象,首先需要创建一个 JsonElement
实例。例子中是返回了一个 JsonObject 实例来代表 Book 对象,如下所示:

final JsonObject jsonObject = new JsonObject();

该对象使用我们设置的字段名称进行填充,如下:

// The variable 'book' is passed as a parameter to the serialize() method
    final JsonObject jsonObject = new JsonObject();
    jsonObject.addProperty("title", book.getTitle());
    jsonObject.addProperty("isbn-10", book.getIsbn10());
    jsonObject.addProperty("isbn-13", book.getIsbn13());

使用 addProperty() 方法 (Java
Doc))可以添加任何
Java 原始类型以及String和Number。注意此处的 name
必须是唯一的,否则会被前一个覆盖掉。可以将其看做是一个将字段名作为值索引的
Map。

更复杂的对象,比如 Java
对象或数组就不能使用上面的方法来添加了。JsonObject 有另外一个 add()
方法,可以用来作为替代,如下所示:

// The variable 'book' is passed as a parameter to the serialize() method
    jsonObject.addProperty("title", book.getTitle());
    jsonObject.addProperty("isbn-10", book.getIsbn10());
    jsonObject.addProperty("isbn-13", book.getIsbn13());

    final JsonArray jsonAuthorsArray = new JsonArray();
    for (final String author : book.getAuthors()) {
      final JsonPrimitive jsonAuthor = new JsonPrimitive(author);
      jsonAuthorsArray.add(jsonAuthor);
    }
    jsonObject.add("authors", jsonAuthorsArray);

首先创建一个 JsonArray 对象,然后将所有 authors 添加进去。和 Java
不同的是,初始化 JsonArray
时不需要指定数组的大小。事实上,抛开这个类的名字不看,可以将 JsonArray
类更多地看做是一个 list 而非 array。最后将 jsonAuthorsArray  添加到
jsonObject
中。此处也可以在给 jsonAuthorsArray 添加元素之前,将其添加到 jsonObject 中。

在调用该序列化方法之前,我们需要将其注册到 Gson 中:

package com.javacreed.examples.gson.part1;

import java.io.IOException;

import com.google.gson.Gson;
import com.google.gson.GsonBuilder;

public class Main {

  public static void main(final String[] args) throws IOException {
    // Configure GSON
    final GsonBuilder gsonBuilder = new GsonBuilder();
    gsonBuilder.registerTypeAdapter(Book.class, new BookSerialiser());
    gsonBuilder.setPrettyPrinting();
    final Gson gson = gsonBuilder.create();

    final Book javaPuzzlers = new Book();
    javaPuzzlers.setTitle("Java Puzzlers: Traps, Pitfalls, and Corner Cases");
    javaPuzzlers.setIsbn10("032133678X");
    javaPuzzlers.setIsbn13("978-0321336781");
    javaPuzzlers.setAuthors(new String[] { "Joshua Bloch", "Neal Gafter" });

    // Format to JSON
    final String json = gson.toJson(javaPuzzlers);
    System.out.println(json);
  }
}

通过注册我们自己实现的序列化器,告诉 Gson 无论什么时候序列化 Book
类型的对象都使用该序列化器进行序列化。

在上面例子中,通过调用 set prettying printing 方法还告诉了 Gson 对生成的
JSON 对象进行格式化,如下所示:

gsonBuilder.setPrettyPrinting();

虽然这对于调试和教程非常有用,但请不要在生产环境中这样用,因为可能会因此产生更大的
JSON 对象(文本的大小)。除此之外,由于 Gson 必须要格式化 JSON
对象,即对其进行相应的缩进,pretty printing 会有一些性能方面的消耗。

运行上面的代码可以得到预期的 JSON
对象。对我们的第一个例子做个总结,即怎样自定义 Gson 序列化器将 Java
对象序列化为 JSON 对象。下一章将会讲到怎样使用 Gson 序列化嵌套对象。

Gson中的一些注解

嵌套对象

接下来的例子将会描述怎么序列化嵌套对象。所谓嵌套对象是指在其它对象内部的对象。在此我们将会引入一个新的实体:author。形成了这样一个包含
title 和 ISBN 连同 author 列表的
book。在这个例子中将会得到一个包含新实体的 JSON
对象,与前面的JSON对象不同,就像下面那样:

{
  "title": "Java Puzzlers: Traps, Pitfalls, and Corner Cases",
  "isbn": "032133678X",
  "authors": [
    {
      "id": 1,
      "name": "Joshua Bloch"
    },
    {
      "id": 2,
      "name": "Neal Gafter"
    }
  ]
}

注意,前一个例子中 authors 只是一个简单的字符数组:

"authors": [
    "Joshua Bloch",
    "Neal Gafter"
  ]

这个例子中的 authors 是一个 JSON 对象,而不仅仅只是一个基本类型。

{
      "id": 1,
      "name": "Joshua Bloch"
    }

澳门新葡亰3522平台游戏,author 的 JSON对象有一个 id 和一个 name字段。下面是 Author 类。

package com.javacreed.examples.gson.part2;

public class Author {

  private int id;
  private String name;

  // Methods removed for brevity

}

这个类非常简单,由两个字段组成,并且都是原始类型。Book 类被修改为使用
Author 类,如下所示:

package com.javacreed.examples.gson.part2;

public class Book {

  private Author[] authors;
  private String isbn;
  private String title;

  // Methods removed for brevity

}

author 字段从一个 integer 数组变成了一个 Author 数组。因此必须修改下
BookSerialiser 类来兼容这一改变,如下:

package com.javacreed.examples.gson.part2;

import java.lang.reflect.Type;

import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.google.gson.JsonSerializationContext;
import com.google.gson.JsonSerializer;

public class BookSerialiser implements JsonSerializer<Book> {

  @Override
  public JsonElement serialize(final Book book, final Type typeOfSrc, final JsonSerializationContext context) {
    final JsonObject jsonObject = new JsonObject();
    jsonObject.addProperty("title", book.getTitle());
    jsonObject.addProperty("isbn", book.getIsbn());

    final JsonElement jsonAuthros = context.serialize(book.getAuthors());
    jsonObject.add("authors", jsonAuthros);

    return jsonObject;
  }
}

authors 的序列化由 context(作为 serialize()
方法的一个参数被传进来,是 JsonSerializationContext 的实例)
来完成。context 将会序列化给出的对象,并返回一个 JsonElement。同时
context
也会尝试找到一个可以序列化当前对象的序列化器。如果没有找到,其将会使用默认的序列化器。目前,我们还不会为
Author 类实现一个序列化器,仍然会使用默认实现作为替代。

package com.javacreed.examples.gson.part2;

import java.io.IOException;

import com.google.gson.Gson;
import com.google.gson.GsonBuilder;

public class Main {

  public static void main(final String[] args) throws IOException {
    // Configure GSON
    final GsonBuilder gsonBuilder = new GsonBuilder();
    gsonBuilder.registerTypeAdapter(Book.class, new BookSerialiser());
    gsonBuilder.setPrettyPrinting();
    final Gson gson = gsonBuilder.create();

    final Author joshuaBloch = new Author();
    joshuaBloch.setId(1);
    joshuaBloch.setName("Joshua Bloch");

    final Author nealGafter = new Author();
    nealGafter.setId(2);
    nealGafter.setName("Neal Gafter");

    final Book javaPuzzlers = new Book();
    javaPuzzlers.setTitle("Java Puzzlers: Traps, Pitfalls, and Corner Cases");
    javaPuzzlers.setIsbn("032133678X");
    javaPuzzlers.setAuthors(new Author[] { joshuaBloch, nealGafter });

    final String json = gson.toJson(javaPuzzlers);
    System.out.println(json);
  }
}

上面的例子创建并配置 Gson 使用自定义的 BookSerialiser
进行序列化。我们创建了两个 author 对象和一个 book 对象,并序列化该 book
对象。这样将会得到在章节开始时展示的 JSON 对象。

完整起见,下面是一个 Author 类的序列化器:

package com.javacreed.examples.gson.part2;

import java.lang.reflect.Type;

import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.google.gson.JsonSerializationContext;
import com.google.gson.JsonSerializer;

public class AuthorSerialiser implements JsonSerializer<Author> {

  @Override
  public JsonElement serialize(final Author author, final Type typeOfSrc, final JsonSerializationContext context) {
    final JsonObject jsonObject = new JsonObject();
    jsonObject.addProperty("id", author.getId());
    jsonObject.addProperty("name", author.getName());

    return jsonObject;
  }
}

上面的序列化器并没有引入什么新的特性,因此不需要再多做解释。如果想要使用这个新的序列器,需要将其注册到
GsonBuilder (Java
Doc) 中。

本章对嵌套对象的序列化做了总结。可以将嵌套对象的序列化交给 context
处理,其在序列化时会顺带尝试找到一个合适的序列化器,并返回相应的
JsonElement。下一章也是最后一章将会讲如何处理对象引用的序列化。

1 @SerializedName注解

该注解能指定该字段在JSON中对应的字段名称

public class Box {

  @SerializedName("w")
  private int width;

  @SerializedName("h")
  private int height;

  @SerializedName("d")
  private int depth;

  // Methods removed for brevity
}

也就是说{"w":10,"h":20,"d":30} 这个JSON
字符串能够被解析到上面的width,height和depth字段中。

对象引用

一个对象对其它对象的引用叫做对象引用,book 和 author
类之间的关系就是这样。同一个 author 可以有多本 book。例如 author Joshua
Bloch(Author at
Amazon)不止一本
book。在使用序列化器描述之前,我们将会清除重复的 author。

考虑下面这个例子:

package com.javacreed.examples.gson.part3;

import java.io.IOException;

import com.google.gson.Gson;
import com.google.gson.GsonBuilder;

public class Example1 {

  public static void main(final String[] args) throws IOException {
    // Configure GSON
    final GsonBuilder gsonBuilder = new GsonBuilder();
    gsonBuilder.setPrettyPrinting();
    final Gson gson = gsonBuilder.create();

    final Author joshuaBloch = new Author();
    joshuaBloch.setId(1);
    joshuaBloch.setName("Joshua Bloch");

    final Author nealGafter = new Author();
    nealGafter.setId(2);
    nealGafter.setName("Neal Gafter");

    final Book javaPuzzlers = new Book();
    javaPuzzlers.setTitle("Java Puzzlers: Traps, Pitfalls, and Corner Cases");
    javaPuzzlers.setIsbn("032133678X");
    javaPuzzlers.setAuthors(new Author[] { joshuaBloch, nealGafter });

    final Book effectiveJava = new Book();
    effectiveJava.setTitle("Effective Java (2nd Edition)");
    effectiveJava.setIsbn("0321356683");
    effectiveJava.setAuthors(new Author[] { joshuaBloch });

    final Book[] books = new Book[] { javaPuzzlers, effectiveJava };

    final String json = gson.toJson(books);
    System.out.println(json);
  }
}

两个作者和两本书,其中一个作者在两本书中都有。注意,有两个 author
对象而不是三个,代表 Joshua Bloch 的实例被两个 book
对象共享。最后注意,我们故意没有使用任何自定义的序列化器。下面是执行上面代码的结果。

[
  {
    "authors": [
      {
        "id": 1,
        "name": "Joshua Bloch"
      },
      {
        "id": 2,
        "name": "Neal Gafter"
      }
    ],
    "isbn": "032133678X",
    "title": "Java Puzzlers: Traps, Pitfalls, and Corner Cases"
  },
  {
    "authors": [
      {
        "id": 1,
        "name": "Joshua Bloch"
      }
    ],
    "isbn": "0321356683",
    "title": "Effective Java (2nd Edition)"
  }
]

得到了两个 book 类型的 JSON 和三个 author 类型的
JSON。而对于author:Joshua Bloch 是重复的。

 {
        "id": 1,
        "name": "Joshua Bloch"
      }

这会使得 JSON 对象明显变大,尤其是对更复杂的对象。理想情况下,book 的
JSON 对象应该只包含 author 的 id 而不是整个对象。这类似于关系型数据库中
book 表有一个外键关联到 author 表。

Book 类将会被修改为包含一个只返回 author 的 id 的方法,如下:

package com.javacreed.examples.gson.part3;

public class Book {

  private Author[] authors;
  private String isbn;
  private String title;

  public Author[] getAuthors() {
    return authors;
  }

  public int[] getAuthorsIds() {
    final int[] ids = new int[authors.length];
    for (int i = 0; i < ids.length; i++) {
      ids[i] = authors[i].getId();
    }
    return ids;
  }// Other methods removed for brevity

}

当序列化 book 时,使用 author 的 id 代替整个 author 对象,如下:

package com.javacreed.examples.gson.part3;

import java.lang.reflect.Type;

import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.google.gson.JsonSerializationContext;
import com.google.gson.JsonSerializer;

public class BookSerialiser implements JsonSerializer {

  @Override
  public JsonElement serialize(final Book book, final Type typeOfSrc, final JsonSerializationContext context) {
    final JsonObject jsonObject = new JsonObject();
    jsonObject.addProperty("title", book.getTitle());
    jsonObject.addProperty("isbn", book.getIsbn());

    final JsonElement jsonAuthros = context.serialize(book.getAuthorsIds());
    jsonObject.add("authors", jsonAuthros);

    return jsonObject;
  }
}

下面是使用这个序列化器得到的结果:

[
  {
    "title": "Java Puzzlers: Traps, Pitfalls, and Corner Cases",
    "isbn": "032133678X",
    "authors": [
      1,
      2
    ]
  },
  {
    "title": "Effective Java (2nd Edition)",
    "isbn": "0321356683",
    "authors": [
      1
    ]
  }
]

现在我们使用 author 的 id 代替了整个
author。这一做法使得其比前一个有更小的空间占用。对于更大的对象将产生巨大的影响。

这个例子是整篇文章关于序列化的总结。从中我们学会了怎么使用默认或自定义的序列化选项,将
Java 对象序列化为 JSON 字符串。

2 @Expose注解

该注解能够指定该字段是否能够序列化或者反序列化,默认的是都支持(true)。

public class Account {

  @Expose(deserialize = false)
  private String accountNumber;

  @Expose
  private String iban;

  @Expose(serialize = false)
  private String owner;

  @Expose(serialize = false, deserialize = false)
  private String address;

  private String pin;
}

需要注意的通过
builder.excludeFieldsWithoutExposeAnnotation()方法是该注解生效。

  final GsonBuilder builder = new GsonBuilder();
    builder.excludeFieldsWithoutExposeAnnotation();
    final Gson gson = builder.create();