Gson full resolution (middle) - the use of TypeAdapter

About TypeAdapter

Ahead Gson complete analysis (I) We understand and use JsonSerializer and JsonDeserializer to transform JSON and java entity classes. Here we use TypeAdapter to fulfill this requirement more efficiently.

JsonSerializer mentioned in the previous article
Both JsonElement and JsonDeserializer use a Middleware - JsonElement, such as the following serialization process. You can see that we use this middleware JsonElement when we convert Java objects into JSON strings

JsonElement as the middle layer of resolution

The use of TypeAdapter is to remove this middle layer and directly use flow to parse data, which greatly improves the efficiency of parsing.

New applications should prefer TypeAdapter, whose streaming API is more efficient than this interface's tree API.
TypeAdapter should be used as much as possible in the application. Its streaming API will be more efficient than the previous tree parsing API.

TypeAdapter provides two abstract methods as an abstract class. write() and read() methods, respectively, also correspond to serialization and deserialization. As shown in the figure below:

Let's use and understand TypeAdapter together:

TypeAdapter instance

For the sake of understanding, let's use the same example as the above article.
Book.java entity class:

package com.javacreed.examples.gson.part1;

public class Book {

  private String[] authors;
  private String isbn;
  private String title;

//For code simplicity, remove getter and setter methods, etc
}

Paste the code directly, specifically serialize and deserialize the TypeAdapter class. Here is 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();
  }
}

It is also necessary to configure (register) the TypeAdapter after setting it here. It can be noted that the gsonBuilder.registerTypeAdapter(xxx) method is also used for registration in our previous JsonSerializer and JsonDeserializer:

    final GsonBuilder gsonBuilder = new GsonBuilder();
    gsonBuilder.registerTypeAdapter(Book.class, new BookTypeAdapter());
    final Gson gson = gsonBuilder.create();

The following describes the two write methods and read methods respectively:

write method in typeadapter

The JsonWriter will be passed in the write() method, and the instance of the Book object to be serialized will be written to the JsonWriter in a similar way to PrintStream.

  @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();
  }

Here are the steps of the above code:

  • out.beginObject() generates {. If we want to generate an array object, the corresponding beginary() is used
  • out.name("isbn").value(book.getIsbn()); out.name("title").value(book.getTitle()); get the isbn and title fields in the book respectively and set them to the isbn and title in the JSON object. In other words, the above code will be generated in the JSON object:
"isbn": "978-0321336781",
 "title": "Java Puzzlers: Traps, Pitfalls, and Corner Cases",
  • out.name("authors").value(StringUtils.join(book.getAuthors(), ";")); corresponds to:
 "authors": "Joshua Bloch;Neal Gafter"
  • Similarly, out.endObject() corresponds to}
  • Then the whole code above will also generate JSON objects:
{
  "isbn": "978-0321336781",
  "title": "Java Puzzlers: Traps, Pitfalls, and Corner Cases",
  "authors": "Joshua Bloch;Neal Gafter"
}
  • Note here that if you do not call out.endObject() to generate}, your project will report a JsonSyntaxException error

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 read method in typeadapter

The read() method will pass in an instance of the JsonReader object and return the deserialized object.

  @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;
  }

Here are the steps of this Code:

  • It also resolves {,} through in.beginObject(); and in.endObject()
  • adopt
    while (in.hasNext()) {
      switch (in.nextName()) {
      }
    }

To complete the traversal of each JsonElement, and obtain the key value pairs in the Json object through the method of switch...case. And set it through the Setter method of our Book entity class.

    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;
      }
    }
  • It should also be noted that if in.endObject() is not executed, there will be an error of 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)

The complete code to use the TypeAdapter is as follows:

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

The corresponding compilation results are:

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 handles simple JSON data

In order to simplify JSON data, in fact, the above JSON data can be written as follows:

["978-0321336781","Java Puzzlers: Traps, Pitfalls, and Corner Cases","Joshua Bloch","Neal Gafter"]

As you can see, this is in the form of values. Of course, this operation simplifies JSON data, but may reduce the stability of the whole data. You need to parse the data in a certain order.
The corresponding write and read methods are as follows:

  @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;
  }

The analytical principle here is consistent with the above, and will not be repeated.

TypeAdapter resolving built-in objects

(here, the nested objects are translated into built-in objects, which are actually in the Book class.)

Here, the above Book entity class is modified as follows. Add the Author class. Each Book can have multiple authors.

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;

//For code simplicity, remove getter and setter methods, etc
}
//For code simplicity, remove getter and setter methods, etc
}

JSON objects are provided here,

{
  "isbn": "978-0321336781",
  "title": "Java Puzzlers: Traps, Pitfalls, and Corner Cases",
  "authors": [
    {
      "id": 1,
      "name": "Joshua Bloch"
    },
    {
      "id": 2,
      "name": "Neal Gafter"
    }
  ]
}

Here are the write and read methods:

  @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;
  }

summary

TypeAdapter can operate on serialization and deserialization between JSON and Java objects through the above methods. In fact, when resolving the serialization and deserialization of built-in objects, we can also operate through JsonDeserializer or JsonSerializer. The serialization process is as follows:

@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;
  }

Here, the context object provided by JsonSerializationContext is directly parsed, which to some extent provides the consistency of JSON object serialization (deserialization).


< the above content is reprinted, and the following is the original author's information >


By Anthony
Links: https://www.jianshu.com/p/8cc857583ff4
Source: Jianshu

Published 3 original articles, won praise 3, visited 2807
Private letter follow

Tags: Java Google JSON Apache

Posted on Wed, 12 Feb 2020 06:50:55 -0500 by unmash