Gson全解析(中)-TypeAdapter的使用
TypeAdapter介绍
前面的Gson全解析(上)中我们理解并分别运用了JsonSerializer和JsonDeserializer进行JSON和java实体类之间的相互转化。这里利用TypeAdapter来更加高效的完成这个需求。
之前在上一篇文中提到的JsonSerializer
和JsonDeserializer解析的时候都利用到了一个中间件-JsonElement,比如下方的序列化过程。可以看到我们在把Java对象转化为JSON字符串的时候都会用到这个中间件JsonElement
而TypeAdapter的使用正是去掉了这个中间层,直接用流来解析数据,极大程度上提高了解析效率。
New applications should prefer TypeAdapter, whose streaming API is more efficient than this interface’s tree API.
应用中应当尽量使用TypeAdapter,它流式的API相比于之前的树形解析API将会更加高效。
TypeAdapter作为一个抽象类提供两个抽象方法。分别是write()和read()方法,也对应着序列化和反序列化。如下图所示:

下面就让我们来一起使用和了解TypeAdapter吧:
TypeAdapter实例
为了便于理解,这里还是统 一 一 下,采用和上面一篇文章同样的例子。Book.java实体类:
package com.javacreed.examples.gson.part1;
public class Book {
  private String[] authors;
  private String isbn;
  private String title;
//为了代码简洁,这里移除getter和setter方法等
}
直接贴代码,具体序列化和反序列化的TypeAdapter类,这里是BookTypeAdapter.java:
package com.javacreed.examples.gson.part1;
import java.io.IOException;
import org.apache.commons.lang3.StringUtils;
import com.google.gson.TypeAdapter;
import com.google.gson.stream.JsonReader;
import com.google.gson.stream.JsonWriter;
public class BookTypeAdapter extends TypeAdapter {
  @Override
  public Book read(final JsonReader in) throws IOException {
    final Book book = new Book();
    in.beginObject();
    while (in.hasNext()) {
      switch (in.nextName()) {
      case "isbn":
        book.setIsbn(in.nextString());
        break;
      case "title":
        book.setTitle(in.nextString());
        break;
      case "authors":
        book.setAuthors(in.nextString().split(";"));
        break;
      }
    }
    in.endObject();
    return book;
  }
  @Override
  public void write(final JsonWriter out, final Book book) throws IOException {
    out.beginObject();
    out.name("isbn").value(book.getIsbn());
    out.name("title").value(book.getTitle());
    out.name("authors").value(StringUtils.join(book.getAuthors(), ";"));
    out.endObject();
  }
}
同样这里设置TypeAdapter之后还是需要配置(注册),可以注意到的是gsonBuilder.registerTypeAdapter(xxx)方法进行注册在我们之前的JsonSerializer和JsonDeserializer中也有使用:
    final GsonBuilder gsonBuilder = new GsonBuilder();
    gsonBuilder.registerTypeAdapter(Book.class, new BookTypeAdapter());
    final Gson gson = gsonBuilder.create();
下面对两个write方法和read方法进行分别的阐述:
1 TypeAdapter中的write方法
write()方法中会传入JsonWriter,和需要被序列化的Book对象的实例,采用和PrintStream类似的方式 写入到JsonWriter中。
  @Override
  public void write(final JsonWriter out, final Book book) throws IOException {
    out.beginObject();
    out.name("isbn").value(book.getIsbn());
    out.name("title").value(book.getTitle());
    out.name("authors").value(StringUtils.join(book.getAuthors(), ";"));
    out.endObject();
  }
下面是上面代码的步骤:
out.beginObject()产生{,如果我们希望产生的是一个数组对象,对应的使用beginArray()out.name("isbn").value(book.getIsbn()); out.name("title").value(book.getTitle());分别获取book中的isbn和title字段并且设置给Json对象中的isbn和title。也就是说上面这段代码,会在json对象中产生:"isbn": "978-0321336781",
"title": "Java Puzzlers: Traps, Pitfalls, and Corner Cases",out.name("authors").value(StringUtils.join(book.getAuthors(), ";"));则会对应着:"authors": "Joshua Bloch;Neal Gafter"- 同理 
out.endObject()则对应着} - 那么整个上面的代码也就会产生JSON对象:
{
"isbn": "978-0321336781",
"title": "Java Puzzlers: Traps, Pitfalls, and Corner Cases",
"authors": "Joshua Bloch;Neal Gafter"
} 这里需要注意的是,如果没有调用
out.endObject()产生},那么你的项目会报出JsonSyntaxException错误Exception in thread "main" com.google.gson.JsonSyntaxException: java.io.EOFException: End of input at line 4 column 40
at com.google.gson.Gson.fromJson(Gson.java:813)
at com.google.gson.Gson.fromJson(Gson.java:768)
at com.google.gson.Gson.fromJson(Gson.java:717)
at com.google.gson.Gson.fromJson(Gson.java:689)
at com.javacreed.examples.gson.part1.Main.main(Main.java:41)
Caused by: java.io.EOFException: End of input at line 4 column 40
at com.google.gson.stream.JsonReader.nextNonWhitespace(JsonReader.java:1377)
at com.google.gson.stream.JsonReader.doPeek(JsonReader.java:471)
at com.google.gson.stream.JsonReader.hasNext(JsonReader.java:403)
at com.javacreed.examples.gson.part1.BookTypeAdapter.read(BookTypeAdapter.java:33)
at com.javacreed.examples.gson.part1.BookTypeAdapter.read(BookTypeAdapter.java:1)
at com.google.gson.Gson.fromJson(Gson.java:803)
... 4 more
2 TypeAdapter中的read方法
read()方法将会传入一个JsonReader对象实例并返回反序列化的对象。
  @Override
  public Book read(final JsonReader in) throws IOException {
    final Book book = new Book();
    in.beginObject();
    while (in.hasNext()) {
      switch (in.nextName()) {
      case "isbn":
        book.setIsbn(in.nextString());
        break;
      case "title":
        book.setTitle(in.nextString());
        break;
      case "authors":
        book.setAuthors(in.nextString().split(";"));
        break;
      }
    }
    in.endObject();
    return book;
  }
下面是这段代码的步骤:
- 同样是通过
in.beginObject();和in.endObject();对应解析{,} - 通过
while (in.hasNext()) {
switch (in.nextName()) {
}
}来完成每个
JsonElement的遍历,并且通过switch...case的方法获取Json对象中的键值对。并通过我们Book实体类的Setter方法进行设置。 while (in.hasNext()) {
switch (in.nextName()) {
case "isbn":
book.setIsbn(in.nextString());
break;
case "title":
book.setTitle(in.nextString());
break;
case "authors":
book.setAuthors(in.nextString().split(";"));
break;
}
}同样需要注意的是,如果没有执行
in.endObject(),将会出现JsonIOException的错误:Exception in thread "main" com.google.gson.JsonIOException: JSON document was not fully consumed.
at com.google.gson.Gson.assertFullConsumption(Gson.java:776)
at com.google.gson.Gson.fromJson(Gson.java:769)
at com.google.gson.Gson.fromJson(Gson.java:717)
at com.google.gson.Gson.fromJson(Gson.java:689)
at com.javacreed.examples.gson.part1.Main.main(Main.java:41)
下面给出使用TypeAdapter的完整代码:
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 {
    final GsonBuilder gsonBuilder = new GsonBuilder();
    gsonBuilder.registerTypeAdapter(Book.class, new BookTypeAdapter());
    gsonBuilder.setPrettyPrinting();
    final Gson gson = gsonBuilder.create();
    final Book book = new Book();
    book.setAuthors(new String[] { "Joshua Bloch", "Neal Gafter" });
    book.setTitle("Java Puzzlers: Traps, Pitfalls, and Corner Cases");
    book.setIsbn("978-0321336781");
    final String json = gson.toJson(book);
    System.out.println("Serialised");
    System.out.println(json);
    final Book parsedBook = gson.fromJson(json, Book.class);
    System.out.println("\nDeserialised");
    System.out.println(parsedBook);
  }
}
对应的编译结果为:
Serialised
{
  "isbn": "978-0321336781",
  "title": "Java Puzzlers: Traps, Pitfalls, and Corner Cases",
  "authors": "Joshua Bloch;Neal Gafter"
}
Deserialised
Java Puzzlers: Traps, Pitfalls, and Corner Cases [978-0321336781]
Written by:
  >> Joshua Bloch
  >> Neal Gafter
TypeAdapter处理简洁的JSON数据
为了简化JSON数据,其实我们上面的JSON数据可以这么写:
["978-0321336781","Java Puzzlers: Traps, Pitfalls, and Corner Cases","Joshua Bloch","Neal Gafter"]
可以看到的是,这样采用的直接是值的形式。当然这样操作简化了JSON数据但是可能就让整个数据的稳定性下降了许多的,你需要按照一定的顺序来解析这个数据。
对应的write和read方法如下:
  @Override
  public void write(final JsonWriter out, final Book book) throws IOException {
    out.beginArray();
    out.value(book.getIsbn());
    out.value(book.getTitle());
    for (final String author : book.getAuthors()) {
      out.value(author);
    }
    out.endArray();
  }
  @Override
  public Book read(final JsonReader in) throws IOException {
    final Book book = new Book();
    in.beginArray();
    book.setIsbn(in.nextString());
    book.setTitle(in.nextString());
    final List authors = new ArrayList<>();
    while (in.hasNext()) {
      authors.add(in.nextString());
    }
    book.setAuthors(authors.toArray(new String[authors.size()]));
    in.endArray();
    return book;
  }
这里的解析原理和上面一致,不再赘述。
TypeAdapter解析内置对象
(这里将nested objects翻译为内置对象,其实就是在Book类)
这里对上面的Book实体类进行修改如下,添加Author作者类,每本书可以有多个作者。
package com.javacreed.examples.gson.part3;
public class Book {
  private Author[] authors;
  private String isbn;
  private String title;
class Author {
  private int id;
  private String name;
//为了代码简洁,这里移除getter和setter方法等
}
//为了代码简洁,这里移除getter和setter方法等
}
这里提供JSON对象,
{
  "isbn": "978-0321336781",
  "title": "Java Puzzlers: Traps, Pitfalls, and Corner Cases",
  "authors": [
    {
      "id": 1,
      "name": "Joshua Bloch"
    },
    {
      "id": 2,
      "name": "Neal Gafter"
    }
  ]
}
下面分别展示write和read方法:
  @Override
  public void write(final JsonWriter out, final Book book) throws IOException {
    out.beginObject();
    out.name("isbn").value(book.getIsbn());
    out.name("title").value(book.getTitle());
    out.name("authors").beginArray();
    for (final Author author : book.getAuthors()) {
      out.beginObject();
      out.name("id").value(author.getId());
      out.name("name").value(author.getName());
      out.endObject();
    }
    out.endArray();
    out.endObject();
  }
 @Override
  public Book read(final JsonReader in) throws IOException {
    final Book book = new Book();
    in.beginObject();
    while (in.hasNext()) {
      switch (in.nextName()) {
      case "isbn":
        book.setIsbn(in.nextString());
        break;
      case "title":
        book.setTitle(in.nextString());
        break;
      case "authors":
        in.beginArray();
        final List authors = new ArrayList<>();
        while (in.hasNext()) {
          in.beginObject();
          final Author author = new Author();
          while (in.hasNext()) {
            switch (in.nextName()) {
            case "id":
              author.setId(in.nextInt());
              break;
            case "name":
              author.setName(in.nextString());
              break;
            }
          }
          authors.add(author);
          in.endObject();
        }
        book.setAuthors(authors.toArray(new Author[authors.size()]));
        in.endArray();
        break;
      }
    }
    in.endObject();
    return book;
  }
总结
TypeAdapter对JSON和Java对象之间的序列化和反序列化可以通过上面的方法进行操作。其实在解决解析内置对象的序列化和反序列化的时候我们也可以通过JsonDeserializer或者JsonSerializer进行操作,序列化过程如下:
@Override
  public JsonElement serialize(final Book book, final Type typeOfSrc, final JsonSerializationContext context) {
    final JsonObject jsonObject = new JsonObject();
    jsonObject.addProperty("isbn", book.getIsbn());
    jsonObject.addProperty("title", book.getTitle());
    final JsonElement jsonAuthros = context.serialize(book.getAuthors());
    jsonObject.add("authors", jsonAuthros);
    return jsonObject;
  }
这里通过JsonSerializationContext提供的context对象直接解析,一定程度上提供了JSON对象序列化(反序列化)的一致性。
参考链接
翻译原文,根据原文做出了较大改动。
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使用指南(一)系列文章
更多及时技术资讯,欢迎关注我的微博 :Anthony
本篇文章是基于Gson官方使用指导(Gson User Guide)以及Gson解析的优秀外文(来自http://www.javacreed.com/ )做出的一个翻译和归纳。
博客原链接:
Gson全解析(上)-Gson基础
Gson全解析(中)-TypeAdapter的使用
Gson全解析(下)-Gson性能分析
from: http://www.jianshu.com/p/8cc857583ff4
Gson全解析(中)-TypeAdapter的使用的更多相关文章
- Gson全解析(下)-Gson性能分析
		
前言 在之前的学习中,我们在Gson全解析(上)Gson使用的基础到分别运用了JsonSerializer和JsonDeserializer进行JSON和java实体类之间的相互转化. 在Gson全解 ...
 - Gson全解析(上)-Gson基础
		
前言 最近在研究Retrofit中使用的Gson的时候,发现对Gson的一些深层次的概念和使用比较模糊,所以这里做一个知识点的归纳整理. Gson(又称Google Gson)是Google公司发布的 ...
 - Java7/8 中的 HashMap 和 ConcurrentHashMap 全解析
		
Java7/8 中的 HashMap 和 ConcurrentHashMap 全解析 今天发一篇”水文”,可能很多读者都会表示不理解,不过我想把它作为并发序列文章中不可缺少的一块来介绍.本来以为花不了 ...
 - CSS 中z-index全解析(摘自阿里西西)
		
z-index全解析 Z-index属性决定了一个HTML元素的层叠级别.元素层叠级别是相对于元素在Z轴上(与X轴Y轴相对照)的位置而言.一个更高的Z-index值意味着这个元素在叠层顺序中会更靠近顶 ...
 - C# 嵌入dll  动软代码生成器基础使用  系统缓存全解析  .NET开发中的事务处理大比拼  C#之数据类型学习  【基于EF Core的Code First模式的DotNetCore快速开发框架】完成对DB First代码生成的支持  基于EF Core的Code First模式的DotNetCore快速开发框架  【懒人有道】在asp.net core中实现程序集注入
		
C# 嵌入dll 在很多时候我们在生成C#exe文件时,如果在工程里调用了dll文件时,那么如果不加以处理的话在生成的exe文件运行时需要连同这个dll一起转移,相比于一个单独干净的exe,这种形 ...
 - 截取HTML中的JSON数据并利用GSON进行解析(Android)
		
截取HTML中的JSON数据并利用GSON进行解析(Android) 前言 最近在做的一个Android项目,需要自行搭建服务器,队友选择买了阿里云的服务器ESC产品,在数据获取上,我们采用了Andr ...
 - Java并发指南13:Java7/8 中的 HashMap 和 ConcurrentHashMap 全解析
		
Java7/8 中的 HashMap 和 ConcurrentHashMap 全解析 转自https://www.javadoop.com/post/hashmap#toc7 部分内容转自 http: ...
 - Java并发原理层面:ReentrantLock中lock()、unlock()全解析
		
一.前言 Java线程同步两种方式,synchronized关键字和Lock锁机制,其中,AQS队列就是Lock锁实现公平加锁的底层支持. 二.AQS源码对于lock.lock()的实现 2.1 AQ ...
 - OkHttp使用全解析(转)。
		
Android系统提供了两种HTTP通信类,HttpURLConnection和HttpClient.关于HttpURLConnection和HttpClient的选择>>官方博客尽管Go ...
 
随机推荐
- TestNG测试方法
			
@Test(enabled = false)有助于禁用此测试用例. 分组测试是TestNG中的一个新的创新功能,使用<groups>标记在testng.xml文件中指定分组. 它可以在&l ...
 - php 中使用include、require、include_once、require_once的区别
			
在PHP中,我们经常会通过include.require.include_once.require_once来引用文件,都可以达到引用文件的目的,但他们之间又有哪些区别呢,接一下我们详细的介绍一下 i ...
 - JavaScript事件属性event.target
			
<!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <title> ...
 - python-随机操作(random)
			
random模块作用是返回随机数,只要跟随机元素相关的,都可以使用它. Python标准库中的random函数,可以生成随机浮点数.整数.字符串,甚至帮助你随机选择列表序列中的一个元素,打乱一组数据等 ...
 - (二)使用CXF开发WebService服务器端接口
			
CXF作为java领域主流的WebService实现框架,Java程序员有必要掌握它. CXF主页:http://cxf.apache.org/ 简介:百度百科 今天的话,主要是用CXF来开发下Web ...
 - python 全栈开发,Day53(jQuery的介绍,jQuery的选择器,jQuery动画效果)
			
js总结 js: 1.ECMAScript5 ES5语法 2.DOM CRUD 获取 3种方式 id tag className //面向对象 对象 : 属性和方法 某个对象中 function $( ...
 - 传统DOM事件处理程序
			
传统DOM事件处理程序与比HTML事件处理程序相比,优点:可以将HTML和JS脚本分离. 它的操作形式如下 : <body> <div>传统DOM事件处理程序与比HTML事件处 ...
 - ruby学习-字符串
			
字符串 1.创建字符1:new用来创建新字符,empty?检验字符是否为空 title = String.new #=> "" title.empty? #=>true ...
 - WCF客户端从服务器下载数据
			
1.打开VS选择控制台项目新建一个解决方案Server,然后添加两个类库Contract和Service. 2.在Contract中添加一个接口IFileDownload using System; ...
 - hdu 1875 给出每个结点的坐标 权值为两点间的距离 (MST)
			
Sample Input2210 10 //坐标20 2031 12 21000 1000 Sample Output1414.2 //最小权值和*100 保留1位小数oh! //不 ...