Gson全解析(中)-TypeAdapter的使用

TypeAdapter介绍

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

之前在上一篇文中提到的JsonSerializer
JsonDeserializer解析的时候都利用到了一个中间件-JsonElement,比如下方的序列化过程。可以看到我们在把Java对象转化为JSON字符串的时候都会用到这个中间件JsonElement

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)方法进行注册在我们之前的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)

下面给出使用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数据但是可能就让整个数据的稳定性下降了许多的,你需要按照一定的顺序来解析这个数据。
对应的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对象序列化(反序列化)的一致性。


<以上内容为转载,以下为原作者信息>[个人GitHub相关操作代码参看:https://github.com/liquan953243897/algorithm.git]


作者:安东尼_Anthony
链接:https://www.jianshu.com/p/8cc857583ff4
来源:简书

  • 0
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Gson 是 Google 提供的一个用来在 Java 对象和 JSON 数据之间进行映射的 Java 类库,它可以将 JSON 字符串转换为 Java 对象,也可以将 Java 对象转换为 JSON 字符串。下面是使用 Gson解析 JSON 数据的示例代码: 1. 添加 GSON 依赖 在项目的 build.gradle 文件添加以下依赖: ```groovy dependencies { // ... implementation 'com.google.code.gson:gson:2.8.6' // ... } ``` 2. 定义 Java 类 首先,我们需要定义一个 Java 类来表示 JSON 数据的结构。假设我们要解析的 JSON 数据格式如下: ```json { "name": "张三", "age": 20, "isMale": true } ``` 那么,我们可以定义一个对应的 Java 类: ```java public class Person { private String name; private int age; private boolean isMale; // 构造函数、getters 和 setters 略 // ... } ``` 3. 解析 JSON 数据 接下来,我们可以使用 Gson 类的`fromJson()`方法将 JSON 字符串转换为 Java 对象。示例代码如下: ```java String jsonData = "{\"name\":\"张三\",\"age\":20,\"isMale\":true}"; // 替换成你要解析的 JSON 数据 Gson gson = new Gson(); Person person = gson.fromJson(jsonData, Person.class); ``` 以上代码,`fromJson()`方法的第一个参数是要解析的 JSON 字符串,第二个参数是要转换为的 Java 类的类型。Gson 会自动将 JSON 的字段映射到 Java 对象的属性。 现在,`person`对象就包含了从 JSON 字符串解析出来的数据。我们可以通过调用`person.getName()`、`person.getAge()`等方法获取属性的值。 注意:使用 Gson 解析 JSON 数据的前提是,JSON 字符串的字段名要与 Java 对象的属性名匹配。如果不匹配,需要通过`@SerializedName`注解或`FieldNamingStrategy`接口来指定对应关系。具体使用方法可以参考 Gson 的文档。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值