澳门新浦京8455comJava Json API:Gson序列化

澳门新浦京8455com 3

Java Json
API:Gson使用简单入门

gson
github地址google/gson

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

本文对应的项目是MultiTypeJsonParser
,项目地址
https://github.com/sososeen09/MultiTypeJsonParser

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


0 前奏

使用 Gson
去解析 json 应该是很常见的,大部分的情况下我们只要创建一个 Gson
对象,然后根据 json 和对应的 Java 类去解析就可以了。

Gson gson = new Gson();
Person person = gson.form(json,Person.class);

但是对于比较复杂的 json,比如下面这种, attributes 对应的 jsonObject
中的字段是完全不一样的,这个时候再简单的用上面的方法就解析不了了。

{
    "total": 2,
    "list": [
        {
            "type": "address",
            "attributes": {
                "street": "NanJing Road",
                "city": "ShangHai",
                "country": "China"
            }
        },
        {
            "type": "name",
            "attributes": {
                "first-name": "Su",
                "last-name": "Tu"
            }
        }
    ]
}

当然了,我们说一步到位的方式解决不了,但用一点笨方法还是可以的。比如先手动解析拿到
attributes 对应的 jsonObject,根据与它同级 type 对应的 value
就可以判断这一段 jsonObject 对应的 Java 类是哪个,最后就采用
gson.from() 方法解析出 attributes 对应的 Java 对象。

ListInfoWithType listInfoWithType = new ListInfoWithType();

//创建 org.json 包下的 JSONObject 对象
JSONObject jsonObject = new JSONObject(TestJson.TEST_JSON_1);
int total = jsonObject.getInt("total");

//创建 org.json 包下的 JSONArray 对象
JSONArray jsonArray = jsonObject.getJSONArray("list");
Gson gson = new Gson();
List<AttributeWithType> list = new ArrayList<>();

//遍历
for (int i = 0; i < jsonArray.length(); i++) {
    JSONObject innerJsonObject = jsonArray.getJSONObject(i);
    Class<? extends Attribute> clazz;
    String type = innerJsonObject.getString("type");
    if (TextUtils.equals(type, "address")) {
        clazz = AddressAttribute.class;
    } else if (TextUtils.equals(type, "name")) {
        clazz = NameAttribute.class;
    } else {
        //有未知的类型就跳过
        continue;
    }
    AttributeWithType attributeWithType = new AttributeWithType();

//采用Gson解析
    Attribute attribute = gson.fromJson(innerJsonObject.getString("attributes"), clazz);
    attributeWithType.setType(type);
    attributeWithType.setAttributes(attribute);
    list.add(attributeWithType);
}

listInfoWithType.setTotal(total);
listInfoWithType.setList(list);

虽然这样能实现整个 json
的反序列化,但是这种方式比较麻烦,而且一点也不优雅,如果项目中存在很多这样的情况,就会做很多重复的体力劳动。
如何更优雅、更通用的解决这类问题,在网上没有找到答案,只好去深入研究一下Gson了。带着这样的目的,翻看了Gson的文档,发现了一句话

Gson can work with arbitrary Java objects including pre-existing
objects that you do not have source code of.

这句话说 Gson 可以处理任意的 Java
对象。那么对于上面讲的那种反序列化情况来讲, Gson 应该也能做到。通过研究
Gson 的文档,发现可以通过自定义JsonDeserializer的方式来实现解析这种
jsonObject 类型不同的情况。

我们知道,大部分情况下 Gson 是通过直接 new
出来的方式来创建,不过也可以采用 GsonBuilder 这个类去生成 Gson。

  Gson gson = new GsonBuilder()
   .registerTypeAdapter(Id.class, new IdTypeAdapter())
   .enableComplexMapKeySerialization()
   .serializeNulls()
   .setDateFormat(DateFormat.LONG)
   .setFieldNamingPolicy(FieldNamingPolicy.UPPER_CAMEL_CASE)
   .setPrettyPrinting()
   .setVersion(1.0)
   .create();

GsonBuilder 通过
registerTypeAdapter()方法,对目标类进行注册。当序列化或者反序列化目标类的时候就会调用我们注册的typeAdapter,
这样就实现了人工干预 Gson 的序列化和反序列化过程。

GsonBuilder 的 registerTypeAdapte() 方法的第二个参数是 Object
类型,也就意味着我们可以注册多种类型的 typeAdapter,目前支持的类型有
JsonSerializer、JsonDeserializer、InstanceCreator、TypeAdapter。

  public GsonBuilder registerTypeAdapter(Type type, Object typeAdapter) 

经过一番捣鼓,写了一个工具类,对于上面的那个复杂
json,用了不到10行代码就搞定,而且比较优雅和通用。

MultiTypeJsonParser<Attribute> multiTypeJsonParser = new MultiTypeJsonParser.Builder<Attribute>()
        .registerTypeElementName("type")
        .registerTargetClass(Attribute.class)
        .registerTargetUpperLevelClass(AttributeWithType.class)
        .registerTypeElementValueWithClassType("address", AddressAttribute.class)
        .registerTypeElementValueWithClassType("name", NameAttribute.class)
        .build();

ListInfoWithType listInfoWithType = multiTypeJsonParser.fromJson(TestJson.TEST_JSON_1, ListInfoWithType.class);

本文就简单分析一下如何通过自定义 JsonDeserializer
来实现一个通用的工具类用于解析复杂类型
json。对于以后碰到相似问题,这种处理方法可以提供一种解决问题的思路。具体的代码和实例,可以查看项目。如果对您的思路有一些启发,欢迎交流和Star。

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

前言

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

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


1 JsonDeserializer介绍

JsonDeserializer 是一个接口,使用的时候需要实现这个接口并在 GsonBuilder
中对具体的类型去注册。当反序列化到对应的类的时候就会调用这个自定义
JsonDeserializer 的 deserialize()
方法。下面对这个方法的几个参数做一下解释,以便于更好的理解Gson解析的过程。

public interface JsonDeserializer<T> {
  public T deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context)
      throws JsonParseException;
}

注意

基本概念

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

1.1 JsonElement

JsonElement代表 在 Gson
中的代表一个元素。它是一个抽象类,有4个子类:JsonObject、JsonArray、JsonPrimitive、JsonNull。
1.JsonObject 表示的是包含name-value型的 json 字符串,其中 name
是字符串,而 value 可以是其它类型的 JsonElement 元素。在json中用
“{}”澳门新浦京8455com , 包裹起来的一个整体就是JsonObject。例如

// "attributes" 是name,后面跟着的{}内容是它对应的value,而这个value就是一个JsonObject
  "attributes": {
                  "first-name": "Su",
                  "last-name": "Tu"
                 }

2.JsonArray 这个类在 Gson
中代表一个数组类型,一个数组就是JsonElement的集合,这个集合中每一个类型都可能不同。这是一个有序的集合,意味着元素的添加顺序是被维持着的。上面例子中list对应的
“[]” 包裹起来的json就是JsonArray。

3.**JsonPrimitive **
这个可以认为是json中的原始类型的值,包含Java的8个基本类型和它们对应的包装类型,也包含
String 类型。比如上面 “first-name” 对应的 “Su” 就是一个 String 类型的
JsonPrimitive 。

4.JsonNull 通过名字也可以猜到,这个代表的是 null 值。

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

Gson解决的问题

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

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

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

  4. 支持任意的复杂对象

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


1.2 Type

Type是Java中的所有类型的顶层接口,它的子类有
GenericArrayType、ParameterizedType、TypeVariable、WildcardType,这个都是在java.lang.reflect包下面的类。另外,我们最熟悉的一个类
Class 也实现了 Type 接口。

一般来讲,调用 GsonBuilder 的 registerTypeAdapter()
去注册,第一个参数使用 Class 类型就可以了。

下面列出的所有代码都可以在这里找到: 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(局部类,可以理解为在方法内部声明的类))的某个字段和外部类的某个字段一样的话,就会被忽视,不会被序列化或者反序列化


1.3 JsonDeserializationContext

这个类是在反序列过程中,由其它类调用我们自定义的 JsonDeserialization 的
deserialize() 方法时传递过来的,在 Gson
中它唯一的一个实现是TreeTypeAdapter 中的一个私有的内部类 GsonContextImpl
。可以在自定义的 JsonDeserializer 的 deserialize() 中去调用
JsonDeserializationContext 的 deserialize() 方法去获得一个对象。

但是要记住,如果传递到 JsonDeserializationContext 中的 json 与
JsonDeserializer 中的 json 一样的话,可能会导致死循环调用。

简单的例子

考虑下面这个 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的类型

澳门新浦京8455com 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中的一些注解

2 思路分析

嵌套对象

接下来的例子将会描述怎么序列化嵌套对象。所谓嵌套对象是指在其它对象内部的对象。在此我们将会引入一个新的实体: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"
    }

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字段中。

2.1 创建JavaBean

还是以最上面的那个 json 进行分析,在 list 对应 JsonArray
,其中的两个 JsonObject 中,attributes 对应的 JsonObject
字段完全不一样,但是为了统一,在写 JavaBean
的时候可以给它们设置一个共同的父类,尽管它是空的。

public class Attribute {
      ...
}

public class AddressAttribute extends Attribute {
    private String street;
    private String city;
    private String country;
... 省略get/set
}

public class NameAttribute extends Attribute {
    @SerializedName("first-name")
    private String firstname;
    @SerializedName("last-name")
    private String lastname;
...省略get/set
}

设置 Attribute 这个 SuperClass 只是为了在 GsonBuilder
去注册,当具体解析的时候我们会根据
type 对应的类型去找到对应的Class。

 gsonBuilder.registerTypeAdapter(Attribute.class, new AttributeJsonDeserializer());

到了这里我们就应该想到,type 对应的 value 肯定是要与具体的 JavaBean
对应起来的。比如在这里就是

"address"——AddressAttribute.class
"name"——NameAttribute.class

如果 type 是 “address” ,那么我们就可以用 gson 去拿
AddressAttribute.class 和对应的 json 去解析。

Attribute attribute = gson.form(addressJson,AddressAttribute.class);

对象引用

一个对象对其它对象的引用叫做对象引用,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();

2.2 如何把 json 准确的转为对应的 JavaBean

我们注册的是父类 Attribute ,当反序列化需要解析 Attribute
的时候就会把对应的 json 作为参数回调自定义的 JsonDeserializer
。我们就可以在下面这个方法中写自己的逻辑得到我们需要的 Attribute
对象了。

 public Attribute deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context)

但是细心的朋友应该会发现了,这个时候传递的 json 有可能是这样的

{
   "street": "NanJing Road",
   "city": "ShangHai",
   "country": "China"
}

也有可能是这样的

{
   "first-name": "Su",
   "last-name": "Tu"
}

我们怎么知道该解析成 AddressAttribute 还是 NameAttribute ???

我们想想,具体解析成哪个,我们肯定是需要知道 type 对应的 value 。而这个
type 是与 attributes 同级的字段,照着刚才这样肯定是没希望拿到这个 value
的。

我们再想想,能够知道这个 type 对应的 value 是什么的肯定是 attributes
上一层级的 json 。

{
   "type": "name",
   "attributes": {
                          ...
                 }  
}

那么我们可不可以在 GsonBuilder 中再去注册一个 typeAdapter
来解析这个外层的 json 呢?当然可以。

 gsonBuilder.registerTypeAdapter(AttributeWithType.class, new AttributeWithTypeJsonDeserializer());

这个 AttributeWithType 就是外层的 json 对应的 JavaBean

public class AttributeWithType {
    private String type;
    private Attribute attributes;
     ...
}

在反序列化 AttributeWithType 这个类的时候,我们可以获得这个 type 对应的
value,然后把这个 value 传递给里层的 Attribute 对应的
JsonDeserializer。这样就可以根据 value 是 “address” 或者 “name” 去对
AddresAttribute 或者 NameAttribute 进行反序列化了。

3 @Since和@Until注解

Since代表“自从”,Until
代表”一直到”。它们都是针对该字段生效的版本。比如说
@Since(1.2)代表从版本1.2之后才生效,@Until(0.9)代表着在0.9版本之前都是生效的。

public class SoccerPlayer {

  private String name;

  @Since(1.2)
  private int shirtNumber;

  @Until(0.9)
  private String country;

  private String teamName;

  // Methods removed for brevity
}

也就是说我们利用方法builder.setVersion(1.0)定义版本1.0,如下:

 final GsonBuilder builder = new GsonBuilder();
    builder.setVersion(1.0);

    final Gson gson = builder.create();

    final SoccerPlayer account = new SoccerPlayer();
    account.setName("Albert Attard");
    account.setShirtNumber(10); // Since version 1.2
    account.setTeamName("Zejtun Corinthians");
    account.setCountry("Malta"); // Until version 0.9

    final String json = gson.toJson(account);
    System.out.printf("Serialised (version 1.0)%n  %s%n", json);

由于shirtNumbercountry作用版本分别是1.2之后,和0.9之前,所以在这里都不会得到序列化,所以输出结果是:

Serialised (version 1.0)
  {"name":"Albert Attard","teamName":"Zejtun Corinthians"}

2.3 有一个坑

前面那我们讲过,调用 JsonDeserializationContext
的方法应该注意死循环。在具体的实践中,我虽然没有调用
JsonDeserializationContext
的方法,但是依然出现了死循环的情况。就是因为我是这么用的。

 AttributeWithType attributeWithType = gson.fromJson(json, AttributeWithType.class);

乍一看没什么问题啊,问题就出在这个 gson 身上。这个 gson 是已经注册过解析
AttributeWithType 的 GsonBuilder 创建的。 gson.fromJson() 方法中的 json
是 AttributeWithType 对应的反序列化的 json,gson.fromJson()
内部会再次调用 AttributeWithType 对应的 JsonDeserializer 中的
deserialize() 方法,从而导致死循环。

避免死循环的方式就是用GsonBuilder新建一个 gson ,这个GsonBuilder不再注册
AttributeWithType ,而只去注册 Attribute 去解析。

Gson 序列化

英文Serialize和format都对应序列化,这是一个Java对象到JSON字符串的过程。
接着看一个例子,下面分别是java类和以及我们期望的JSON数据:

public class Book {
  private String[] authors;
  private String isbn10;
  private String isbn13;
  private String title;
  //为了代码简洁,这里移除getter和setter方法等

}

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

你肯定能发现JSON数据中出现了isbn-10isbn-13,
我们怎么把字段数据isbn10isbn13转化为JSON数据需要的isbn-10isbn-13,Gson当然为我们提供了对应的解决方案

3 为了更好更通用

1.在项目中,可能还会存在另一种格式的json,外部没有单独的type元素,而是与其它的元素放在同一个JsonObject中。这样的格式更省事,不需要注册外层的typeAdaper即可。

{
    "total": 2,
    "list": [
        {
            "type": "address",
            "street": "NanJing Road",
            "city": "ShangHai",
            "country": "China"
        },
        {
            "type": "name",
            "first-name": "Su",
            "last-name": "Tu"
        }
    ]
}

MultiTypeJsonParser<Attribute> multiTypeJsonParser = new MultiTypeJsonParser.Builder<Attribute>()
        .registerTypeElementName("type")
        .registerTargetClass(Attribute.class)
// 如果所要解析的 jsonObejct 中已经含有能够表示自身类型的字段,不需要注册外层 Type,这样更省事
//        .registerTargetUpperLevelClass(AttributeWithType.class)
        .registerTypeElementValueWithClassType("address", AddressAttribute.class)
        .registerTypeElementValueWithClassType("name", NameAttribute.class)
        .build();

ListInfoWithType listInfoWithType = multiTypeJsonParser.fromJson(TestJson.TEST_JSON_1, ListInfoWithType.class);

2.如果在解析过程中发现有些类型没有注册到 MultiTypeJsonParser 的 Builder
中,解析的时候碰到相应的 jsonObject
就直接返回null。比如下面这样的json中,”type” 对应的 “parents”
如果没有注册,那么反序列化的时候这个 json 所代表的对象就为 null 。

 {
        "type": "parents",
        "attributes": {
          "mather": "mi lan",
          "father": "lin ken"
        }
 }

在Android中我们反序列这样的 json
后一般会把得到的对象的设置到列表控件上,如果后端返回的 json
中包含之前未注册的类型,为了程序不至于 crash,需要对反序列化的 null
对象进行过滤,项目中提供了一个工具类 ListItemFilter 可以过滤集合中为
null 的元素。

1 序列化方案1

采用上面提到的@SerializedName注解。

public class Book {
  private String[] authors;

  @SerializedName("isbn-10")
  private String isbn10;

  @SerializedName("isbn-13")
  private String isbn13;
  private String title;
  //为了代码简洁,这里移除getter和setter方法等

}

4 结语

对于如何优雅的解析这种类型不同的 JsonObject
,刚开始我是缺少思路的,在网上也没有查到合适的文档。但是通过查看 Gson
的文档和源码,通过自己的理解和分析,逐步的完成了这个过程。我的一个感触就是,多去看看官方的使用文档应该比盲目去搜索解决方案更好。

代码是最好的文档,本文只简单介绍了一些实现思路,文中贴出的一些代码是为了讲述方便,与项目中的代码可能会有有些区别。具体的使用可以看项目中的例子。

如果有问题,欢迎提 issue 或留言,如果对您有所帮助,欢迎Star。

2 序列化方案2

利用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;
    }
}

下面对序列化过程进行大致的分析:

  • JsonSerializer是一个接口,我们需要提供自己的实现,来满足自己的序列化要求。

public interface JsonSerializer<T> {

  /**
   *Gson 会在解析指定类型T数据的时候触发当前回调方法进行序列化
   *
   * @param T 需要转化为Json数据的类型,对应上面的Book
   * @return 返回T指定的类对应JsonElement
   */
  public JsonElement serialize(T src, Type typeOfSrc, JsonSerializationContext context);
}
  • 首先在上面的代码中,我们需要创建的是一个JsonElement对象,这里对应Book是一个对象,所以创建一个JsonObject类型。
    final JsonObject jsonObject = new JsonObject();
  • 然后我们将相应字段里面的数据填充到jsonObject里面。

jsonObject.addProperty...
jsonObject.add...

下面是jsonObject中的添加方法:

澳门新浦京8455com 2

  • 所以最后返回的还是一个JsonElement
    类型,这里对应的是jsonObject。完成了javaBean->JSON数据的转化。

  • 同样需要配置,

// 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);

,这里对应的是
gsonBuilder.registerTypeAdapter(Book.class, new BookSerialiser())方法进行JsonSerializer的配置。在上面例子中,通过调用gsonBuilder.setPrettyPrinting();方法还告诉了
Gson 对生成的 JSON 对象进行格式化


参考

Gson官方文档

Gson 反序列化

英文parse和deserialise对应反序列化,这是一个字符串转换成Java对象的过程。
我们同样采用上面一小节的代码片段,只不过现在我们需要做的是将:

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

转化为对应的Book实体类,

1 反序列化方案1

利用@SerializedName 注解
也就是说我们的实体类Book.java可以这么写:

public class Book {
  private String[] authors;

  @SerializedName("isbn-10")
  private String isbn10;

  @SerializedName(value = "isbn-13", alternate = {"isbn13","isbn.13"})
  private String isbn13;
  private String title;
  //为了代码简洁,这里移除getter和setter方法等

}

澳门新浦京8455com 3

可以看到这里我们在@SerializedName 注解使用了一个value,
alternate字段,value也就是默认的字段,对序列化和反序列化都有效,alternate只有反序列化才有效果。也就是说一般服务器返回给我们JSON数据的时候可能同样的一个图片,表示”image”,”img”,”icon”等,我们利用@SerializedName
中的alternate字段就能解决这个问题,全部转化为我们实体类中的图片字段。

2 反序列化方案2

我们在序列化的时候使用的是JsonSerialize
,这里对应使用JsonDeserializer
我们将解析到的json数据传递给Book的setter方法即可。

public class BookDeserializer implements JsonDeserializer<Book> {

  @Override
  public Book deserialize(final JsonElement json, final Type typeOfT, final JsonDeserializationContext context)
      throws JsonParseException {
    final JsonObject jsonObject = json.getAsJsonObject();

    final JsonElement jsonTitle = jsonObject.get("title");
    final String title = jsonTitle.getAsString();

    final String isbn10 = jsonObject.get("isbn-10").getAsString();
    final String isbn13 = jsonObject.get("isbn-13").getAsString();

    final JsonArray jsonAuthorsArray = jsonObject.get("authors").getAsJsonArray();
    final String[] authors = new String[jsonAuthorsArray.size()];
    for (int i = 0; i < authors.length; i++) {
      final JsonElement jsonAuthor = jsonAuthorsArray.get(i);
      authors[i] = jsonAuthor.getAsString();
    }

    final Book book = new Book();
    book.setTitle(title);
    book.setIsbn10(isbn10);
    book.setIsbn13(isbn13);
    book.setAuthors(authors);
    return book;
  }
}

和Gson序列化章节一样,我们这里接着分析我们是怎么将JSON数据解析(反序列化)为实体类的:

  • 因为我们可以发现上面的JSON数据是一个{}大括号包围的,也就意味着这是一个Json对象。所以首先我们通过
    final JsonObject jsonObject = json.getAsJsonObject();将我们的JsonElement转化为JsonObject
  • 通过jsonObject.get("xxx").getAsString()的形式获取相应String的值
  • 通过jsonObject.get("xx").getAsJsonArray();获取相应的json数组,并遍历出其中的相应字段值
  • 通过setter方法,将获取到的值设置给Book类。
  • 最终返回的是 Book的对象实例。完成了JSON->javaBean的转化
  • 同样需要配置
  • 关于从本地流中读取Json数据可以使用 InputStreamReader完成

 // Configure Gson
    GsonBuilder gsonBuilder = new GsonBuilder();
    gsonBuilder.registerTypeAdapter(Book.class, new BookDeserializer());
    Gson gson = gsonBuilder.create();

    // The JSON data
    try(Reader reader = new InputStreamReader(Main.class.getResourceAsStream("/part1/sample.json"), "UTF-8")){

      // Parse JSON to Java
      Book book = gson.fromJson(reader, Book.class);
      System.out.println(book);
    }

参考链接

翻译原文,根据原文做出了较大改动。
1 SIMPLE GSON
EXAMPLE
2 GSON DESERIALISER
EXAMPLE
3 GSON ANNOTATIONS
EXAMPLE
4 GSON SERIALISER
EXAMPLE
5 GSON TYPEADAPTER
EXAMPLE
6 GSON TYPEADAPTER EXAMPLE SERIALISE LARGE
OBJECTS

另附:
你真的会用Gson吗?Gson使用指南(一)系列文章

You can leave a response, or trackback from your own site.

Leave a Reply

网站地图xml地图