Gson全解析(中)

Posted CameloeAnthony

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Gson全解析(中)相关的知识,希望对你有一定的参考价值。

gson github地址google/gson

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


TypeAdapter介绍

前面的Gson全解析(上)中我们理解并分别运用了JsonSerializer和JsonDeserializer进行JSON和java实体类之间的相互转化。这里利用TypeAdapter来更加高效的完成同样的这个功能。

之前在上一篇文中提到的JsonSerializer
 和 JsonDeserializer解析的时候都利用到了一个中间件-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实例

为了便于理解,这里还是统一一下,采用和上面一篇文章同样的例子。
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)方法进行注册在我们之前的JsonSerializerJsonDeserializer中也有使用:

    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)

下面给出完整代码:

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数据但是可能就让整个数据的稳定性下降了许多的,你需要按照一定的顺序来解析这个数据。
对应的writeread方法如下:

  @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对象序列化(反序列化)的一致性。

以上是关于Gson全解析(中)的主要内容,如果未能解决你的问题,请参考以下文章

Gson全解析(上)

Gson全解析(上)-Gson基础

Gson全解析(下)-Gson性能分析

Gson全解析(上)-Gson基础(转)

Gson全解析之一:JsonReader的beginObject()

Gson全解析之二:JsonReader的其它方法