转载

完全理解Gson(3):Gson反序列化

本文延续前一篇 文章 ,继续介绍简单基本的Gson用法。这篇文章我们将介绍如何将复杂的JSON对象解析为Java对象,其中Java对象的结构可以与JSON对象不一致。我们还会看到如何使用Gson反序列化器 JsonDeserializer Java文档

将JSON对象映射为Java对象。

观察

请注意 文中解析(parse)与反序列化(deserialise)将互换使用。

下面列出的所有代码都可以在 https://java-creed-examples.googlecode.com/svn/gson/Gson Deserialiser Example 获取。绝大多数例子都不包含全部代码,与讨论主题不相关的代码片段有可能被忽略。读者可以从上面的链接下载或阅读全部代码。

对于不熟悉Gson的读者,鼓励您先阅读 简单Gson用例 ,熟悉之后再开始阅读本篇内容。

一个简单的实例

比方说,我们有如下JSON对象,它包含两位著名作者的畅销Java书( Amazon )。

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

上面的JSON对象包括4个字段,其中一个是数组。这些字段代表了我们的书籍。使用 简单Gson实例 中讨论的方法可能产生一个问题。默认情况下,Gson期望Java类中的变量名与JSON查找到的名称一样。因此,我们需要包含如下域名的类: title isbn-10isbn-13authors 。但是Java语言规范 ( 第六章 )指出,Java变量名不能包含减号( - )。

我们将在 接下来的实例中看到如何使用 JsonDeserializer 完全控制JSON的解析。另外我们也可以使用 Gson注解实例

中提到的注解。注解控制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对象中的书籍信息。注意,JSON对象有4个字段,每个变量对应一个JSON字段。这两个对象(Java和JSON)的结构不必一致。Java对象的结构可以与JSON对象不同。

为了将JSON对象解析成Java对象,我们需要创建自己的 JsonDeserializer 接口实例,并且注册到 GsonBuilder ( Java文档 )中。下面的例子展示了我们实现的  JsonDeserializer

package com.javacreed.examples.gson.part1; import java.lang.reflect.Type; import com.google.gson.JsonArray; import com.google.gson.JsonDeserializationContext; import com.google.gson.JsonDeserializer; import com.google.gson.JsonElement; import com.google.gson.JsonObject; import com.google.gson.JsonParseException; public class BookDeserializer implements JsonDeserializer<Book> {   @Override   public Book deserialize(final JsonElement json, final Type typeOfT, final JsonDeserializationContext context)    throws JsonParseException {  //The deserialisation code is missing  final Book book = new Book();  book.setTitle(title);  book.setIsbn10(isbn10);  book.setIsbn13(isbn13);  book.setAuthors(authors);  return book;   } } 

上面的例子是不完整的,我们还需要增加最重要的部分——反序列化。在增加更多代码之前,我们先了解一下这个类变复杂之前的版本。

JsonDeserializer 接口需要一个类型,该类型是需要我们解析的对象类型。在这个例子里,我们将JSON对象解析成  Book 类型的Java对象。  deserialize() 方法的返回类型必须与泛型参数一致,为 Book 类型。

Gson将JSON对象解析成一个 JsonElement ( Java文档 )类型的Java对象。一个  JsonElement 实例可以是下面类型之一:

  • JsonPrimitive ( Java Doc ):例如一个字符串或整数。
  • JsonObject ( Java文档 ): JsonElement 的集合,以名称( String 类型)为索引。 与 Map<String, JsonElement> ( Java文档 )相似。
  • JsonArray ( Java文档 ): JsonElement 的集合。注意数组元素可以是任何4中类型,也支持混合类型。
  • JsonNull ( Java文档 ): null 值。

完全理解Gson(3):Gson反序列化

JsonElement的类型

上图显示了所有 JsonElement 的类型。  JsonObject 可以被认为是一个键值对的集合,其中值是 JsonElement 类型。因此,该值可以是其他对象。

完全理解Gson(3):Gson反序列化

Json对象层级

上图以 JsonObject 为根展示了一个JSON对象层级。特别需要注意,不同于Java,JSON支持不同类型的数组。上图中,  JsonArray 包含 JsonObject、JsonArrayJsonPrimitive 。请注意,上图展示的JSON对象层级不反映前面列出的JSON对象。下面前面列出的JSON对象的JSON对象层级。

完全理解Gson(3):Gson反序列化

Book Json对象层级

如果我们反序列化这个JSON对象,首先需要将给定的 JsonElement 转换为一个  JsonObject

// The variable 'json' is passed as a parameter to the deserialize() method final JsonObject jsonObject = json.getAsJsonObject();

使用相似的方法, JsonElement 可以转换成其他任何类型。

JsonObject 中的元素可以使用名称进行检索。例如,要从上面列出的JSON对象检索 title 元素,我们可以进行下面操作。

// The variable 'json' is passed as a parameter to the deserialize() method final JsonObject jsonObject = json.getAsJsonObject(); JsonElement titleElement = jsonObject.get("title")

返回的对象不是一个 String ,而是另一个  JsonElement 。可以调用  getAsString() 方法将  JsonElement 转换为  String ,代码如下:

// The variable 'json' is passed as a parameter to the deserialize() method final JsonObject jsonObject = json.getAsJsonObject(); JsonElement titleElement = jsonObject.get("title") final String title = jsonTitle.getAsString();

下面的例子展示了如何使用定制的反序列化器转换上面列出的JSON对象。

package com.javacreed.examples.gson.part1; import java.lang.reflect.Type; import com.google.gson.JsonArray; import com.google.gson.JsonDeserializationContext; import com.google.gson.JsonDeserializer; import com.google.gson.JsonElement; import com.google.gson.JsonObject; import com.google.gson.JsonParseException; 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;   } } 

上例中,我们检索JSON元素和它的4个字段,并返回了一个 Book 实例。

在可以使用新的反序列化器之前,必须指定Gson使用我们的反序列化器来解析 Book 类型的对象,代码如下:

package com.javacreed.examples.gson.part1;  import java.io.InputStreamReader; import com.google.gson.Gson; import com.google.gson.GsonBuilder;  public class Main {   public static void main(String[] args) throws Exception {     // 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);     }   } }

上例中,我们通过 GsonBuilder 创建了一个  Gson 实例。使用  registerTypeAdapter() 方法向Gson注册了我们的反序列化器,并指定反序列化  Book 类型对象时使用我们定义的反序列化器。当请求Gson反序列化一个  Book 类对象时,Gson将使用我们定义的反序列化器。下面的步骤描述了我们调用  gson.fromJson(data, Book.class) 时发生了什么。

  1.   将输入解析成  JsonElement 对象。注意,即使对象的类型是  JsonElement ,输入可以是任何类型。在这个阶段,JSON对象字符串被反序列化为  JsonElement 类型的Java对象。这个步骤还确保给定JSON数据的有效性。
  2.   检索给定对象的反解析器,本例中是  BookDeserializer 实例。
  3.   调用  deserialize() 方法并提供必需的参数。例子里,将调用我们的  deserialize() 方法。这里 将从给定的  JsonElement 对象创建一个  Book 类型对象。这是Java内部的转化。
  4.   返回  deserialize() 方法的返回值到调用者  fromJson() 方法。这一步像一个链条,Gson从我们的反序列化器接收一个对象并返回给它的调用者。

执行上面的例子可能得到下面的输出:

Java Puzzlers: Traps, Pitfalls, and Corner Cases    [ISBN-10: 032133678X] [ISBN-13: 978-0321336781] Written by:   >> Joshua Bloch   >> Neal Gafter

至此 我们结束了简单的例子。本例是后续更复杂例子的引子。在下一个例子里,我们将讨论当前对象的一个增强版本,该版本的作者对象不仅是一个简单的字符串,而是一个对象。

嵌套对象

本例中,我们将描述如何反序列化嵌套对象,也就是对象包含对象。这里,我们将介绍一个新的实体,作者。一本书,除了有标题和ISBN号,还可以有多个作者。换句话说,每个作者可以写多本书。为了增加新的实体,本例中的JSON对象做了修改,与前例不同:

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

稍微调整了JSON对象的结构并用JSON对象的作者数组代替了之前的原型,如下图:

完全理解Gson(3):Gson反序列化

Book与Authors Json对象层级

我们还是以一本书为例,只是这次我们有了更复杂和更详细的JSON对象。除了 name字段 ,作者对象还有一个  id字段 。为这个模型增加了新类称为  Author ,  Book 类用它保存作者信息。这立即导致如下问题。

如何反序列化新的   Author  类?

这里有几种选择。

  1. 我们可以更新  BookDeserializer  并增加反解析作者信息的代码。这有个限制,它将  Author  的反序列化与  Book 绑定了,因此不推荐这个方法。
  2. 我们可以使用默认的Gson实现,该方法在这个例子中工作正常,因为Java对象( Author 类)和JSON对象有同名的字段,可以进行 简单Gson实例 文中提到的反序列化。
  3.  或者,我们可以写一个  AuthorDeserializer  类,该类会处理Author的反序列化。

我们从第二种选择开始,保证改变最小化,例子尽量简单。然后 我们增加新的反序列化器来展示Gson的灵活性。

JsonDeserializer 提供了一个  JsonDeserializationContext ( Java文档 )实例作为 deserialize() 方法的第三个参数。我们还没有用过这个参数。我们可以将对象的反序列化委托给指定的  JsonDeserializationContext 实例。它将反序列化给定的  JsonElement 并返回一个指定类型的实例,代码如下。

Author author = context.deserialize(jsonElement, Author.class);

上例将 Author 类的反序列化委托给  context 变量。反过来,它试图搜索已注册的可以反序列化  Author 类的  JsonDeserialize 实例,

如果未发现注册的实例,它将使用 简单Gson实例

中提到的默认机制。

我们的例子使用了一个 Author 数组,因此我们需要使用正确的类型,例子如下:

package com.javacreed.examples.gson.part2; import java.lang.reflect.Type; import com.google.gson.JsonArray; import com.google.gson.JsonDeserializationContext; import com.google.gson.JsonDeserializer; import com.google.gson.JsonElement; import com.google.gson.JsonObject; import com.google.gson.JsonParseException; 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 String title = jsonObject.get("title").getAsString();  final String isbn10 = jsonObject.get("isbn-10").getAsString();  final String isbn13 = jsonObject.get("isbn-13").getAsString();  // Delegate the deserialization to the context  Author[] authors = context.deserialize(jsonObject.get("authors"), Author[].class);  final Book book = new Book();  book.setTitle(title);  book.setIsbn10(isbn10);  book.setIsbn13(isbn13);  book.setAuthors(authors);  return book;   } } 

JsonPrimitive 转换成  JsonObject 是十分简单直接的,就像上面例子中看到的那样。

BookDeserialiser类似 ,我们可以编写  ArthurDeserialiser 类并使用处理书籍相似的方式反序列化作者。

package com.javacreed.examples.gson.part2; import java.lang.reflect.Type; import com.google.gson.JsonDeserializationContext; import com.google.gson.JsonDeserializer; import com.google.gson.JsonElement; import com.google.gson.JsonObject; import com.google.gson.JsonParseException; public class AuthorDeserializer implements JsonDeserializer {   @Override   public Author deserialize(final JsonElement json, final Type typeOfT, final JsonDeserializationContext context)    throws JsonParseException {  final JsonObject jsonObject = json.getAsJsonObject();  final Author author = new Author();  author.setId(jsonObject.get("id").getAsInt());  author.setName(jsonObject.get("name").getAsString());  return author;   } } 

为了使用 ArthurDeserialiser ,我们需要向  GsonBuilder 注册,

package com.javacreed.examples.gson.part2;  import java.io.IOException; import java.io.InputStreamReader; import java.io.Reader;  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 BookDeserializer());     gsonBuilder.registerTypeAdapter(Author.class, new AuthorDeserializer());     final Gson gson = gsonBuilder.create();      // Read the JSON data     try (Reader reader = new InputStreamReader(Main.class.getResourceAsStream("/part2/sample.json"), "UTF-8")) {        // Parse JSON to Java       final Book book = gson.fromJson(reader, Book.class);       System.out.println(book);     }   } }

没有必要改变 BookDeserialiser 类,因为作者的反序列化委托给了  context 变量。这是另一个使用  context 反序列化其他对象或嵌套对象的优点。运行上面的代码将产生下面的输出。

Java Puzzlers: Traps, Pitfalls, and Corner Cases [032133678X] Written by:   >> [1] Joshua Bloch   >> [2] Neal Gafter

至此我们结束了嵌套对象的介绍。下一章节我们将看到如何引用JSON对象树中其他位置的JSON对象。

对象引用

考虑下面的JSON。

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

上面的JSON对象由两个作者和两本书组成。书有一个id到作者的引用,本例中书的 authors 字段只包含作者的id。这是一个很常见的场景,通过这个方法将减少JSON对象的大小,因为重复对象通过他们的id进行引用。下图展示了JSON新对象的层级。

完全理解Gson(3):Gson反序列化

JSON新对象层级

这类似于关系型数据库( 维基 ),其中book对象有一个到作者表的外键( 维基 )。新的JSON对象引入了需要解决的新挑战。当反序列化书籍对象时,我们需要保持作者对象,并从JSON对象层级的其他分支反序列化它们。书籍对象只有作者对象的id。作者对象的其他信息不在当前上下文中,需要从其他地方进行解析。

这里有多种方式来解决这个问题,下面列出了一些。

1. 一种方法是分两个阶段处理。首先将JSON对象解析成Java对象,这一步反序列化JSON中的书和作者对象。书籍类包含作者的id数组而不是作者的数组。接着 第二阶段,我们关联对象,将作者对象关联到书籍对象。下图显示了反解析流程。

完全理解Gson(3):Gson反序列化

两阶段处理过程

这个方法需要很多类,但是提供了很大的灵活性并且更好地分离了关注点。我们需要创建一组代表JSON对象的简单Java类,接着创建另一组满足我们需求(模型)的类。这个例子里,我们有一个 Book 和一个 Author 类,总共两个。使用这个方法,我们最终将有4个类,两个代表书籍类,另两个代表作者类。本例使用这个方法似乎是可行的,但是当有数十个类时,就会变得十分复杂。

2. 另一种方法是提供包含所有作者的 BookDeserialiser 类,接着使用反序列化器从公用对象检索所有作者。这种方法消除了中间状态,因为JSON对象没有经过中间阶段就被反序列化成适当的Java对象。

完全理解Gson(3):Gson反序列化

反序列化器共享对象

尽管这个方法听上去有吸引力,但它要求 BookDeserialiser 和  AuthorDeserialiser 共享一个对象。此外,当当检索作者时,  BookDeserialiser 不得不引用这个共享对象来代替之前使用的 JsonDeserializationContext 类。这个方法需要修改几个地方 反序列化器和 main() 函数都需要修改。

3. AuthorDeserialiser 可以换成反序列化的作者,并在下次指定ID的请求时返回它们。这个方法十分有吸引力,因为它充分利用了  JsonDeserializationContext ,并且使得关系透明。不幸的是,它增加了复杂性, AuthorDeserialiser 需要处理缓存。按照这个说法,这种方法需要最少的修改,只有  AuthorDeserialiser 需要修改。

完全理解Gson(3):Gson反序列化

AuthorDeserialiser 反序列化器使用缓存对象

如上如所示,只有 AuthorDeserialiser 类访问缓存对象。系统里的其他部分不知道这一点。

所有方法都是可行的,而且每种都有他们的优缺点。我们将使用第三种方法,因为它对工程的影响最小。

观察

理论上,与另两种方法相比,第一个方法提供了更好地分离了关注点。我们可以在新的  Data 类中处理关联逻辑。但与第三种方法相比,这需要很多修改。这就是使用第三种方法的原因。始终考虑改变,并尽量减少所需的工作。

上面所示的JSON对象包含两个数组。我们需要新的Java类来反射这个JSON对象。

package com.javacreed.examples.gson.part3;  public class Data {    private Author[] authors;   private Book[] books;    // Methods removed for brevity }

字段顺序决定了两个集合反序列化的顺序。在我们的例子中没有问题,在后面我们可以看到,书籍集合可以在作者集合之前被反序列化。

AuthorDeserialiser 类需要做出修改,它会缓存反序列化出的作者对象。

package com.javacreed.examples.gson.part3;  import java.lang.reflect.Type; import java.util.HashMap; import java.util.Map;  import com.google.gson.JsonDeserializationContext; import com.google.gson.JsonDeserializer; import com.google.gson.JsonElement; import com.google.gson.JsonObject; import com.google.gson.JsonParseException; import com.google.gson.JsonPrimitive;  public class AuthorDeserializer implements JsonDeserializer<Author> {    private final ThreadLocal<Map<Integer, Author>> cache = new ThreadLocal<Map<Integer, Author>>() {     @Override     protected Map<Integer, Author> initialValue() {       return new HashMap<>();     }   };    @Override   public Author deserialize(final JsonElement json, final Type typeOfT, final JsonDeserializationContext context)       throws JsonParseException {      // Only the ID is available     if (json.isJsonPrimitive()) {       final JsonPrimitive primitive = json.getAsJsonPrimitive();       return getOrCreate(primitive.getAsInt());     }       // The whole object is available     if (json.isJsonObject()) {       final JsonObject jsonObject = json.getAsJsonObject();        final Author author = getOrCreate(jsonObject.get("id").getAsInt());       author.setName(jsonObject.get("name").getAsString());       return author;     }      throw new JsonParseException("Unexpected JSON type: " + json.getClass().getSimpleName());   }    private Author getOrCreate(final int id) {     Author author = cache.get().get(id);     if (author == null) {       author = new Author();       author.setId(id);       cache.get().put(id, author);     }     return author;   } }

我们在这个类中做了一些修改。让我们一个个地说明。

1.作者保存在下面的对象中

private final ThreadLocal<Map<Integer, Author>> cache = new ThreadLocal<Map<Integer, Author>>() {     @Override     protected Map<Integer, Author> initialValue() {       return new HashMap<>();     }   };

它使用 Map<String, Object> 提供缓存机制。map变量保存在 ThreadLocal ( Java文档 )中,以隔离多线程之间的状态。这个类允许多线程使用相同的变量而不会影响到其他线程。

观察

请注意,尽管这个方法是线程安全的,但它 不满足特定应用领域的需求,因此不得不使用其他方法。更多缓存实例,请参考 如何缓存结果来提高性能 和 缓存使Spring更高效 。

2.总是通过下面方法得到作者。

  1. private Author getOrCreate(final int id) {   Author author = cache.get().get(id);   if (author == null) {     author = new Author();     cache.get().put(id, author);   }   return author; } 

这个方法首先从缓存中获取作者实例,如果没有找到给定id的作者,那么将创建一个并加入到缓存中。

这个方法允许我们只用id就能创建作者,之后当它们可用时公布它们的内容。这就是为什么反序列化属性不影响输出。我们可以先反序列化书籍类,再反序列化作者类。在这个例子中,首先使用id创建作者,然后给他们增加名字。

3.对 deserialize() 函数进行修改来处理新的需求。因为修改了很多地方,我们将拆分这个方法,并逐个讲解。反序列化器可以接收 JsonPrimitive 变量或者 JsonObject 变量。 当 BookDeserialiser 执行下面的代码时,传递给  AuthorDeserialiser 的  JsonElement 变量将会是一个 JsonPrimitive 实例。

// This is executed within the BookDeserialiser Author[] authors = context.deserialize(jsonObject.get("authors"), Author[].class);

下图显示了这个过程。

完全理解Gson(3):Gson反序列化

委托反序列化给上下文

上下文收到 BookDeserialiser 委托反序列化  Author 数组的操作,并返回一个整型数组。对于每个整数,上下文作为一个  JsonPrimitive 对象传递给  AuthorDeserialiser 的  deserialize() 方法。

另一方面,当作者被反序列化后,我们将收到一个包含作者和他或她详细信息的 JsonObject 实例。因此,在我们转换给定的 JsonElement 对象前,需要校验他是否是正确的类型。

// Only the ID is available     if (json.isJsonPrimitive()) {       final JsonPrimitive primitive = json.getAsJsonPrimitive();       final Author author = getOrCreate(primitive.getAsInt());       return author;     }

上例所示,只有id是有效的。 JsonElement 转换为 JsonPrimitive 接着又转换为  int .。

JsonElement 可以是 JsonObject 类型,如下所示。

// The whole object is available     if (json.isJsonObject()) {       final JsonObject jsonObject = json.getAsJsonObject();        final Author author = getOrCreate(jsonObject.get("id").getAsInt());       author.setName(jsonObject.get("name").getAsString());       return author;     }

这个例子中,在返回author前,向 getOrCreate() 方法返回的  Author 实例中增加name字段。

最后,如果给定的 JsonElement 实例既不是  JsonPrimitive 也不是  JsonObject ,将抛出一个异常说明不支持指定类型。

throw new JsonParseException("Unexpected JSON type: " + json.getClass().getSimpleName());

以上代码块列出了所有需要的修改

以适应和应对新的挑战。 BookDeserialiser 类和  main() 方法不需要任何修改。执行 main()

将得到如下输出。

Output missing...

这个例子总结了我们关于Gson反序列化器的文章。使用定制反序列化器不困难,可以使我们毫不费力的处理不同的JSON示例。需要注意的是,在Java业务对象不需要与解析的JSON对象对应。此外,我们可以使用新的JSON表示现有的Java对象。有些问题可能比其他问题解决起来更具挑战性。试着最大程度减少对现有代码的修改,你的设计将更具灵活性(尽可能不要修改)。

原文链接: javacreed 翻译:ImportNew.com -liken

译文链接:[]
正文到此结束
Loading...