文档章节

JSON 序列化与反序列化详解(中)

侠客人生
 侠客人生
发布于 2017/07/14 22:49
字数 7353
阅读 58
收藏 0
点赞 0
评论 0

Gson全解析

Gson基础

前言

最近在研究Retrofit中使用的Gson的时候,发现对Gson的一些深层次的概念和使用比较模糊,所以这里做一个知识点的归纳整理。

Gson(又称Google Gson)是Google公司发布的一个开放源代码的Java库,主要用途为序列化Java对象为JSON字符串,或反序列化JSON字符串成Java对象。而JSON(JavaScript Object Notation) 是一种轻量级的数据交换格式,易于人阅读和编写,同时也易于机器解析和生成,广泛应用于各种数据的交互中,尤其是服务器与客户端的交互。

 

基本概念

· Serialization:序列化,使Java对象到Json字符串的过程。

· Deserialization:反序列化,字符串转换成Java对象。

· JSON数据中的JsonElement有下面这四种类型:
JsonPrimitive —— 例如一个字符串或整型
JsonObject—— 一个以 JsonElement 名字(类型为 String)作为索引的集合。也就是说可以把 JsonObject 看作值为 JsonElement 的键值对集合。
JsonArray—— JsonElement 的集合。注意数组的元素可以是四种类型中的任意一种,或者混合类型都支持。
JsonNull—— 值为null

Gson解决的问题

1) 提供一种像toString()和构造方法的很简单的机制,来实现Java 对象和Json之间的互相转换。

2) 允许已经存在的无法改变的对象,转换成Json,或者Json转换成已存在的对象。

3) 允许自定义对象的表现形式

4) 支持任意的复杂对象

5) 能够生成可压缩和可读的Json的字符串输出。

Gson处理对象的几个重要点

1 推荐把成员变量都声明称private的

2 没有必要用注解(@Expose 注解)指明某个字段是否会被序列化或者反序列化,所有包含在当前类(包括父类)中的字段都应该默认被序列化或者反序列化

3 如果某个字段被 transient 这个Java关键词修饰,就不会被序列化或者反序列化

4 下面的实现方式能够正确的处理null
1)当序列化的时候,如果对象的某个字段为null,是不会输出到Json字符串中的。
2)当反序列化的时候,某个字段在Json字符串中找不到对应的值,就会被赋值为null

5 如果一个字段是 synthetic的,他会被忽视,也即是不应该被序列化或者反序列化

6 内部类(或者anonymous class(匿名类),或者local class(局部类,可以理解为在方法内部声明的类))的某个字段和外部类的某个字段一样的话,就会被忽视,不会被序列化或者反序列化

 

Gson中的一些注解

1.@SerializedName注解

该注解能指定该字段在JSON中对应的字段名称

public class Box {

  @SerializedName("w")

  private int width;

  @SerializedName("h")

  private int height;

  @SerializedName("d")

  private int depth;

  // Methods removed for brevity

}

也就是说 {"w":10,"h":20,"d":30}  这个JSON 字符串能够被解析到上面的width,height和depth字段中。

2.@Expose注解

该注解能够指定该字段是否能够序列化或者反序列化,默认的是都支持(true)。

public class Account {

  @Expose(deserialize = false)

  private String accountNumber;

  @Expose

  private String iban;

  @Expose(serialize = false)

  private String owner;

  @Expose(serialize = false, deserialize = false)

  private String address;

  private String pin;

}

需要注意的通过 builder.excludeFieldsWithoutExposeAnnotation()方法是该注解生效。

  final GsonBuilder builder = new GsonBuilder();

    builder.excludeFieldsWithoutExposeAnnotation();

    final Gson gson = builder.create();

3.@Since和@Until注解

Since代表“自从”,Until 代表”一直到”。它们都是针对该字段生效的版本。比如说@Since(1.2)代表从版本1.2之后才生效,@Until(0.9)代表着在0.9版本之前都是生效的。

public class SoccerPlayer {

  private String name;

  @Since(1.2)

  private int shirtNumber;

  @Until(0.9)

  private String country;

  private String teamName;

  // Methods removed for brevity

}

也就是说我们利用方法builder.setVersion(1.0)定义版本1.0,如下:

 final GsonBuilder builder = new GsonBuilder();

    builder.setVersion(1.0);

    final Gson gson = builder.create();

    final SoccerPlayer account = new SoccerPlayer();

    account.setName("Albert Attard");

    account.setShirtNumber(10); // Since version 1.2

    account.setTeamName("Zejtun Corinthians");

    account.setCountry("Malta"); // Until version 0.9

    final String json = gson.toJson(account);

    System.out.printf("Serialised (version 1.0)%n  %s%n", json);

由于shirtNumber和country作用版本分别是1.2之后,和0.9之前,所以在这里都不会得到序列化,所以输出结果是:

Serialised (version 1.0)

  {"name":"Albert Attard","teamName":"Zejtun Corinthians"}

Gson 序列化

英文Serialize和format都对应序列化,这是一个Java对象到JSON字符串的过程。
接着看一个例子,下面分别是java类和以及我们期望的JSON数据:

public class Book {

  private String[] authors;

  private String isbn10;

  private String isbn13;

  private String title;

  //为了代码简洁,这里移除getter和setter方法等

}

 

{

  "title": "Java Puzzlers: Traps, Pitfalls, and Corner Cases",

  "isbn-10": "032133678X",

  "isbn-13": "978-0321336781",

  "authors": [

    "Joshua Bloch",

    "Neal Gafter"

  ]

}

你肯定能发现JSON数据中出现了isbn-10和isbn-13, 我们怎么把字段数据isbn10和isbn13转化为JSON数据需要的isbn-10和isbn-13,Gson当然为我们提供了对应的解决方案

1 序列化方案1

采用上面提到的@SerializedName注解。

public class Book {

  private String authors;

  @SerializedName("isbn-10")

  private String isbn10;

  @SerializedName("isbn-13")

  private String isbn13;

  private String title;

  //为了代码简洁,这里移除getter和setter方法等

}

2 序列化方案2

利用JsonSerializer类

public class BookSerialiser implements JsonSerializer<Book> {

    @Override

    public JsonElement serialize(final Book book, final Type typeOfSrc, final JsonSerializationContext context) {

final JsonObject jsonObject = new JsonObject();

jsonObject.addProperty("title", book.getTitle());

jsonObject.addProperty("isbn-10", book.getIsbn10());

jsonObject.addProperty("isbn-13", book.getIsbn13());

final JsonArray jsonAuthorsArray = new JsonArray();

for (final String author : book.getAuthors()) {

final JsonPrimitive jsonAuthor = new JsonPrimitive(author);

jsonAuthorsArray.add(jsonAuthor);

}

jsonObject.add("authors", jsonAuthorsArray);

return jsonObject;

  }

}

下面对序列化过程进行大致的分析:

JsonSerializer是一个接口,我们需要提供自己的实现,来满足自己的序列化要求。

public interface JsonSerializer<T> {

/**

 *Gson 会在解析指定类型T数据的时候触发当前回调方法进行序列化

 * @param T 需要转化为Json数据的类型,对应上面的Book

 * @return 返回T指定的类对应JsonElement

 */

public JsonElement serialize(T src, Type typeOfSrc, JsonSerializationContext context);

}

    首先在上面的代码中,我们需要创建的是一个JsonElement对象,这里对应Book是一个对象,所以创建一个JsonObject类型。
final JsonObject jsonObject = new JsonObject();

· 然后我们将相应字段里面的数据填充到jsonObject里面。

· 对于JsonObject.addProperty()添加后,解析过程是按照我们添加时的先后顺序解析。

jsonObject.addProperty...

jsonObject.add...

· 下面是jsonObject中的添加方法:

所以最后返回的还是一个JsonElement 类型,这里对应的是jsonObject。完成了javaBean->JSON数据的转化。

同样需要配置,

// Configure GSON

  final GsonBuilder gsonBuilder = new GsonBuilder();

  gsonBuilder.registerTypeAdapter(Book.class, new BookSerializer());

  gsonBuilder.setPrettyPrinting();

  final Gson gson = gsonBuilder.create();

 

  final Book javaPuzzlers = new Book();

  javaPuzzlers.setTitle("Java Puzzlers: Traps, Pitfalls, and Corner Cases");

  javaPuzzlers.setIsbn10("032133678X");

  javaPuzzlers.setIsbn13("978-0321336781");

  javaPuzzlers.setAuthors(new String[] { "Joshua Bloch", "Neal Gafter" });

 

  // Format to JSON

  final String json = gson.toJson(javaPuzzlers);

  System.out.println(json);

这里对应的是
gsonBuilder.registerTypeAdapter(Book.class, new BookSerialiser())方法进行JsonSerializer的配置。在上面例子中,通过调用gsonBuilder.setPrettyPrinting();方法还告诉了 Gson 对生成的 JSON 对象进行格式化

Gson 反序列化

英文parse和deserialise对应反序列化,这是一个字符串转换成Java对象的过程。
我们同样采用上面一小节的代码片段,只不过现在我们需要做的是将:

{

  "title": "Java Puzzlers: Traps, Pitfalls, and Corner Cases",

  "isbn-10": "032133678X",

  "isbn-13": "978-0321336781",

  "authors": [

    "Joshua Bloch",

    "Neal Gafter"

  ]

}

转化为对应的Book实体类,

1 反序列化方案1

利用@SerializedName 注解
也就是说我们的实体类Book.java可以这么写:

public class Book {

  private String[] authors;

 

  @SerializedName("isbn-10")

  private String isbn10;

 

  @SerializedName(value = "isbn-13", alternate = {"isbn13","isbn.13"})

  private String isbn13;

  private String title;

  //为了代码简洁,这里移除getter和setter方法等

 

}

可以看到这里我们在@SerializedName 注解使用了一个value, alternate字段,value也就是默认的字段,对序列化和反序列化都有效,alternate只有反序列化才有效果。也就是说一般服务器返回给我们JSON数据的时候可能同样的一个图片,表示"image","img","icon"等,我们利用@SerializedName 中的alternate字段就能解决这个问题,全部转化为我们实体类中的图片字段。

2 反序列化方案2

我们在序列化的时候使用的是JsonSerialize ,这里对应使用JsonDeserializer
我们将解析到的json数据传递给Book的setter方法即可。

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;

  }

}

和Gson序列化章节一样,我们这里接着分析我们是怎么将JSON数据解析(反序列化)为实体类的:

JsonDeserializer

泛型接口JsonDeserializer中的方法deserialize允许我们自定义反序列化过程,返回相对应的对象。

该方法接收三个参数,Gson会在进行相应类型字段的反序列化时回调该方法。

v 第一个参数类型为JsonElement,其中包含了真实返回的数据,它是一个抽象类,可以是JsonObject、JsonArray、JsonPrimitive、JsonNull四者之一,看名字也能知道这四者的大致类型。

v 第二个参数是当前反序列化的数据类型。

v 第三个是上下文参数context。

在自定义反序列化时,我们要分别处理数据类型正确和错误的情况,具体处理过程视数据而定,以下仅供参考

· 因为我们可以发现上面的JSON数据是一个{}大括号包围的,也就意味着这是一个Json对象。所以首先我们通过
final JsonObject jsonObject = json.getAsJsonObject();将我们的JsonElement转化为JsonObject

· 通过jsonObject.get("xxx").getAsString()的形式获取相应String的值

· 通过jsonObject.get("xx").getAsJsonArray();获取相应的json数组,并遍历出其中的相应字段值

· 通过setter方法,将获取到的值设置给Book类。

· 最终返回的是 Book的对象实例。完成了JSON->javaBean的转化

· 同样需要配置

关于从本地流中读取Json数据可以使用 InputStreamReader完成

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

  }

TypeAdapter的使用

我们理解并分别运用了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:

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()

l 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",

l out.name("authors").value(StringUtils.join(book.getAuthors(), ";"));则会对应着:

"authors": "Joshua Bloch;Neal Gafter"

l 同理 out.endObject()则对应着}

l 那么整个上面的代码也就会产生JSON对象:

{"isbn": "978-0321336781","title": "Java Puzzlers: Traps, Pitfalls, and Corner Cases","authors": "Joshua Bloch;Neal Gafter"

}

l 这里需要注意的是,如果没有调用 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;

  }

下面是这段代码的步骤:

l 同样是通过in.beginObject();和in.endObject();对应解析{,}

l 通过

  while (in.hasNext()) {

    switch (in.nextName()) {

    }

  }

l 来完成每个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;

    }

  }

l 同样需要注意的是,如果没有执行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进行操作,序列化过程如下:

package com.jhaso.gson.test8;

import com.google.gson.*;

import java.lang.reflect.Type;
import java.util.ArrayList;
import java.util.List;

/**
 *
 * @ClassName:   BookSerializer
 * @Description: 序列化的时候使用的是JsonSerialize  
 * @author:      jhaso jhaso1988@163.com
 * @date:        2017年7月13日下午2:48:07
 * @version:     V1.0.0
 */
public class BookSerializer implements JsonSerializer<Book> , JsonDeserializer<Book>  {

   /**
    * java -> json
    * @param book
    * @param typeOfSrc
    * @param context
    * @return
    */
   @Override
   public JsonElement serialize(final Book book, final Type typeOfSrc, final JsonSerializationContext context) {

      final JsonObject jsonObject = new JsonObject();
      //转换时会按照addProperty添加的先后顺序解析。
      jsonObject.addProperty("title", book.getTitle());
      jsonObject.addProperty("isbn-10", book.getIsbn10());
      jsonObject.addProperty("isbn-13", book.getIsbn13());
      final JsonElement jsonAuthors = context.serialize(book.getAuthors());
      jsonObject.add("authors", jsonAuthors);
      return jsonObject;
   }

   /**
    * json -> Java
    * @param jsonElement
    * @param type
    * @param jsonDeserializationContext
    * @return
    * @throws JsonParseException
    */
   @Override
   public Book deserialize(JsonElement json, Type type, JsonDeserializationContext jsonDeserializationContext) 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 List<Author> authors = new ArrayList<Author>();
      for (int i = 0; i < jsonAuthorsArray.size(); i++) {
         final int id = jsonAuthorsArray.get(i).getAsJsonObject().get("id").getAsInt();
         final String name = jsonAuthorsArray.get(i).getAsJsonObject().get("name").getAsString();
         authors.add(new Author(id,name));
      }
      final Book book = new Book((Author[]) authors.toArray(new Author[authors.size()]),isbn10, isbn13, title);
      return book;
   }
}

这里通过JsonSerializationContext提供的context对象直接解析,一定程度上提供了JSON对象序列化(反序列化)的一致性。

Gson高级使用和GsonBuilder设置

从JSON的结构入手,所有json数据最终分为三种情况:

1) 标量(Scalar),也就是单纯的字符串或则数字形式

2) 序列(Sequence),也就是若干数据按照一定顺序并列在一起又称“数组”

3) 映射(Mapping),也就是key/value键值对
Json的规格非常简单,此文章就不一一描述:

"{"name":"kalen", "age":22}"

1.默认Gson只能序列和反序列基本数据类型和Date类型,其他类型如枚举都需要自定义解析器registerTypeAdapter
在Android开发是使用:

<dependency>

   <groupId>com.google.code.gson</groupId>

   <artifactId>gson</artifactId>

   <version>2.1</version></dependency>

</dependency>

 

compile 'com.google.code.gson:gson:2.3.1'

Gson 快速使用

普通对象(Mapping)数据解析

String json_str = "{"name":"kalen", "age":22}";

Gson gson = new Gson();

User user = gson.fromGson(json_str, User.class);

通过Gson中的fromGson即可将JSON数据解析并且赋值到User对象中,Gson原理则采用反射机制实现,具体可Google查询Gson原理。

数组数据(Sequence)数据解析

Type listType = new TypeToken<List<String>>() {}.getType();//数组对应gson中的类型

List<String> target = new LinkedList<String>();//gson需要的转换对象或则数据来源

target.add("blah");

Gson gson = new Gson();

String json = gson.toJson(target, listType);

List<String> target2 = gson.fromJson(json, listType);

Gson高级使用

1.GsonBuilder

Gson是通过GsonBuilder生成,设定Gson的序列化和返序列号数据如:

Gson gson = new GsonBuilder()     

.registerTypeAdapter(Id.class, new IdTypeAdapter())   

.enableComplexMapKeySerialization()

.serializeNulls()   

.setDateFormat(DateFormat.LONG)  

.setFieldNamingPolicy(FieldNamingPolicy.UPPER_CAMEL_CASE)//会把字段首字母大写

.setPrettyPrinting()

.setVersion(1.0)    

.create();

以上则是单独对Id类设置了独立解析方式,以及设置时间的解析格式为长时间形式。

2.@Expose注解

如果采用new Gson()方式创建Gson则@Expose则没有任何效果,若采用GsonBuilder创建Gson并且调用了excludeFieldsWithoutExposeAnnotation则@Expose将会影响toJson和fromGson序列化和反序列化数据。如:

public class User {

    @Expose private String firstName;

    @Expose(serialize = false) private String lastName;

    @Expose(serialize = false, deserialize = false)

    private String emailAddress;

    private String password;

 }

例子中password不管是toJson还是fromJson都不会用到,emailAddress和lastName在序列化(fromJson)时将被采用,emailAddress在反序列化(toJson)时将不被采用。

可以通过@SerializedName对序列字段进行重命名,也可以自定义注解然后设置Gson字段解析策略setFieldNamingStrategy,具体在Retrofit Demo中有应用。

3.GsonBuilder方法解释

l setFieldNamingPolicy 设置序列字段的命名策略(UPPER_CAMEL_CASE,UPPER_CAMEL_CASE_WITH_SPACES,LOWER_CASE_WITH_UNDERSCORES,LOWER_CASE_WITH_DASHES)

l addDeserializationExclusionStrategy 设置反序列化时字段采用策略ExclusionStrategy,如反序列化时不要某字段,当然可以采用@Expore代替。

l excludeFieldsWithoutExposeAnnotation 设置没有@Expore则不序列化和反序列化

l addSerializationExclusionStrategy 设置序列化时字段采用策略,如序列化时不要某字段,当然可以采用@Expore代替。

l registerTypeAdapter 为某特定对象设置固定的序列和反序列方式,实现JsonSerializer和JsonDeserializer接口

l setFieldNamingStrategy 设置字段序列和反序列时名称显示,也可以通过@Serializer代替

l setPrettyPrinting 设置gson转换后的字符串为格式化结果。

l setDateFormat 设置默认Date解析时对应的format格式

Eg://注意这里的Gson的构建方式为GsonBuilder,区别于test1中的Gson gson = new Gson();
Gson gson = new GsonBuilder()
.excludeFieldsWithoutExposeAnnotation() //不导出实体中没有用@Expose注解的属性
      .enableComplexMapKeySerialization() //支持Map的key为复杂对象的形式
      .serializeNulls().setDateFormat("yyyy-MM-dd HH:mm:ss:SS")//时间转化为特定格式  
      .setFieldNamingPolicy(FieldNamingPolicy.UPPER_CAMEL_CASE)//会把字段首字母大写,注:对于实体上使用了@SerializedName注解的不会生效.
      .setPrettyPrinting() //对json结果格式化.
      .setVersion(1.0)    //有的字段不是一开始就有的,会随着版本的升级添加进来,那么在进行序列化和返序列化的时候就会根据版本号来选择是否要序列化.
                   //@Since(版本号)能完美地实现这个功能.还的字段可能,随着版本的升级而删除,那么
                   //@Until(版本号)也能实现这个功能,GsonBuilder.setVersion(double)方法需要调用.
      .create();

 

Gson性能分析

Gson性能分析

以下Gson性能分析,内容整理自: GSON TYPEADAPTER EXAMPLE SERIALISE LARGE OBJECTS

采用YourKit 作为性能分析工具。

首先来看看我们提供一个大一点的数据来论证下面一些方法的优缺点。 这里提供类LargeData.java,并分为四个部分进行内存消耗的分析:

public class LargeData {

  private long[] numbers;

  public void create(final int length) {

    numbers = new long[length];

    for (int i = 0; i < length; i++) {

      numbers[i] = i;

    }

  }

  public long[] getNumbers() {

    return numbers;

  }

}

第1部分 JsonSerializer的直接使用

看看下面的JsonSerializer:

package com.javacreed.examples.gson.part1;

import java.lang.reflect.Type;

import com.google.gson.JsonArray;import com.google.gson.JsonElement;import com.google.gson.JsonObject;import com.google.gson.JsonPrimitive;import com.google.gson.JsonSerializationContext;import com.google.gson.JsonSerializer;

public class LargeDataSerialiser implements JsonSerializer<LargeData> {

  @Override

  public JsonElement serialize(final LargeData data, final Type typeOfSrc, final JsonSerializationContext context) {

    final JsonArray jsonNumbers = new JsonArray();

    for (final long number : data.getNumbers()) {

      jsonNumbers.add(new JsonPrimitive(number));

    }

    final JsonObject jsonObject = new JsonObject();

    jsonObject.add("numbers", jsonNumbers);

    return jsonObject;

  }

}

上面的代码实现了从java对象>转化>JSON数组的序列化过程。下面的代码实现了配置和初始化的过程,被写入文件。这里可以看到的是对LargeData初始化了10485760个元素:

package com.javacreed.examples.gson.part1;

import java.io.File;import java.io.IOException;import java.io.PrintStream;

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(LargeData.class, new LargeDataSerialiser());

    gsonBuilder.setPrettyPrinting();

    final Gson gson = gsonBuilder.create();

    final LargeData data = new LargeData();

    data.create(10485760);

    final String json = gson.toJson(data);

    final File dir = new File("target/part1");

    dir.mkdirs();

    try (PrintStream out = new PrintStream(new File(dir, "output.json"), "UTF-8")) {

      out.println(json);

    }

    System.out.println("Done");

  }

}

这个例子实现了创建java对象并且转化为JSON字符串并写入文件的整个过程。下面的图标展示了内存的消耗情况:

JsonSerializer-Profile

上面的的LargeData在这里会消耗89MB的内存,从java对象转化为JSON字符串的过程将会消耗大概16s的时间并且需要超过1GB的内存。也就是说,序列化1MB的数据我们需要大约11MB的工作空间。1:11的确实是一个不小的比列。下面的 图片会展示整个过程的几个阶段。

JsonSerializer-Stages

可以看到的是,这里有四个方块分别代表不同的阶段,(但是IO 缓冲区并没有在这里得到使用,所以以灰色进行标注。)整个过程从java对象(蓝色方块),然后由LargeDataSerialiser类创建的JSONElement对象(红色方块),然后这些临时的对象又被转化为JSON 字符串(绿色方块),上面的示例代码使用PrintStream将内容输出到文件中并没有使用任何缓冲区。

完成了第1部分的分析,接下来下面的分析流程是一样的:

第2 部分 TypeAdapter的直接使用

之前的系列文章中都对Gson基础的使用进行了很好的讲解,可以回顾一下。

TypeAdapter相比 于上面的方法,并没有使用JSONElement对象,而是直接将Java对象啊转化为了JSON对象。

package com.javacreed.examples.gson.part2;

import java.io.IOException;

import com.google.gson.TypeAdapter;import com.google.gson.stream.JsonReader;import com.google.gson.stream.JsonWriter;

 

public class LargeDataTypeAdapter extends TypeAdapter<LargeData> {

 

  @Override

  public LargeData read(final JsonReader in) throws IOException {

    throw new UnsupportedOperationException("Coming soon");

  }

  @Override

  public void write(final JsonWriter out, final LargeData data) throws IOException {

    out.beginObject();

    out.name("numbers");

    out.beginArray();

    for (final long number : data.getNumbers()) {

      out.value(number);

    }

    out.endArray();

    out.endObject();

  }

}

同样会需要配置,这里主要使用的方法是
gsonBuilder.registerTypeAdapter(LargeData.class, new LargeDataTypeAdapter());:

package com.javacreed.examples.gson.part2;

import java.io.File;import java.io.IOException;import java.io.PrintStream;

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(LargeData.class, new LargeDataTypeAdapter());

    gsonBuilder.setPrettyPrinting();

    final Gson gson = gsonBuilder.create();

    final LargeData data = new LargeData();

    data.create(10485760);

    final String json = gson.toJson(data);

    final File dir = new File("target/part2");

    dir.mkdirs();

    try (PrintStream out = new PrintStream(new File(dir, "output.json"), "UTF-8")) {

      out.println(json);

    }

    System.out.println("Done");

  }

}

上面的代码完成的是从java对象 >转化>JSON 字符串并最终写入文件的过程。看看下面的性能分析图表:

TypeAdapter-Profile

和最初的那个方法一样,这里的LargeData对象将会需要89MB的内存,从java对象转化为JSON字符串的过程需要消耗4s的时间,大概650MB的内存。也就是说,序列化1MB的数据,大概需要7.5MB的内存空间。相比于之前的第一种JsonSerializer方法,这里减少了接近一半的内存消耗。同样的,来看看这个方法的几个过程:

TypeAdapter-Stages


这里的序列化过程主要有两个阶段,相比于之前的JSONSerializer的序列化过程,这里没有了转化为JSONElement的过程,也就完成了内存消耗的减少。

第3部分 TypeAdapter的流式处理

下面的代码,我们使用上面同样的TypeAdapter,只不过我们直接在main()方法中修改Gson的用法,以流的形式进行输出。

package com.javacreed.examples.gson.part3;

import java.io.BufferedWriter;import java.io.File;import java.io.FileOutputStream;import java.io.IOException;import java.io.OutputStreamWriter;

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(LargeData.class, new LargeDataTypeAdapter());

    gsonBuilder.setPrettyPrinting();

    final Gson gson = gsonBuilder.create();

    final LargeData data = new LargeData();

    data.create(10485760);

    final File dir = new File("target/part3");

    dir.mkdirs();

    try (BufferedWriter out = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(new File(dir,        "output.json")), "UTF-8"))) {

      gson.toJson(data, out);

    }

    System.out.println("Done");

  }

}

这个例子同样是将java对象转化为JSON字符串并且输出,也来看看下面的性能分析图表:

TypeAdapter-with-Stream-Profile

可以看到的是同样的最初产生的数据是89MB,序列化过程将java对象转化为JSON字符串花了大概三秒钟的时间,消耗大概160MB的内存。也就是说序列化1MB的数据我们需要大概2MB的内存空间。相比于之前的两种方法,有了很大的改进。

TypeAdapter-with-Stream-Stages

这个方法同样的是使用了两个阶段。不过在上面一个示例中的绿色方块部分在这里没有使用,这里直接完成了java对象到IO 缓冲区的转化并写入文件。

虽然这里并不是Gson的关系,但是我们使用Gson的方法极大的减少了内存消耗,所以说在使用开源库的时候,能够正确高效的使用API也显得尤为重要。

第4部分 JsonSerializer 的流式处理

同样的使用第一个例子中的JsonSerializer,这里的配置需要注意的是gsonBuilder.registerTypeAdapter(LargeData.class, new LargeDataSerialiser());

package com.javacreed.examples.gson.part4;

import java.io.BufferedWriter;import java.io.File;import java.io.FileOutputStream;import java.io.IOException;import java.io.OutputStreamWriter;

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(LargeData.class, new LargeDataSerialiser());

    gsonBuilder.setPrettyPrinting();

 

    final Gson gson = gsonBuilder.create();

 

    final LargeData data = new LargeData();

    data.create(10485760);

 

    final File dir = new File("target/part4");

    dir.mkdirs();

 

    try (BufferedWriter out = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(new File(dir,

        "output.json")), "UTF-8"))) {

      gson.toJson(data, out);

    }

 

    System.out.println("Done");

  }

}

经过前面的分析,我们这里也可以这道这里主要分为三个阶段,下面提供性能分析图和JSONSerializer的阶段流程图:

JsonSerializer-with-Stream-Profile

JsonSerializer-with-Stream-Stages

这里可以看到三个阶段完成的工作消耗了11s的时间,730MB的内存空间。也就是说1:8的比例。可以相比上面的例子,知道这里使用JSONSerializer产生了JSONElement对象消耗了很多的内存。

结论

在上面的分析过程中,我们采用了GSON的两种不同的方然完成了序列化一个大数据的过程,并且比较了不同的方法之间的差异。上面的第三种方法(TypeAdapter的流式处理)被论证为最合适的,消耗最少内存的一种方法。

Gson主要分成两部分,一个就是数据拆解,一个是数据封装。

 

© 著作权归作者所有

共有 人打赏支持
侠客人生
粉丝 14
博文 43
码字总数 82954
作品 0
朝阳
fastjson1.2.8原理

fastjson是阿里巴巴的项目,用于进行java对象与JSON字符串之间的序列化(对象转JSON)和反序列化(JSON转对象)。 所以fastjson分为序列化和反序列化两大核心功能,所有API都几种在JSON类中(...

拉风小野驴 ⋅ 2016/03/11 ⋅ 0

Java序列化与JSON序列化大比拼

一、背景 有项目需要传输Map结构的数据,有人倾向用Java序列化来做,有人倾向用JSON的序列化来做。所以我们还是比比吧。 Java观点:Object2Object,使用时简单快速。 JSON观点:JSON格式与语...

NoahX ⋅ 2013/03/10 ⋅ 20

jackson常用注解和spring中的配置

spring默认使用的是jackson处理json的序列化和反序列化,有一些细节和小坑,基于spring4和jackson2.8做一些说明 @JsonIgnore @JsonIgnore是jackson的注解,jackson1版本和2版本没有区别,通常...

肥肥小浣熊 ⋅ 2017/12/01 ⋅ 0

PHP多种序列化以及反序列化

PHP多种序列化以及反序列化 PHP点点通2016-09-2147 阅读 序列化PHP 序列化是将变量转换为可保存或传输的字符串的过程;反序列化就是在适当的时候把这个字符串再转化成原来的变量使用。这两个...

PHP点点通 ⋅ 2016/09/21 ⋅ 0

PHP 序列化变量的 4 种方法

序列化是将变量转换为可保存或传输的字符串的过程;反序列化就是在适当的时候把这个字符串再转化成原来的变量使用。这两个过程结合起来,可以轻松地存储和传输数据,使程序更具维护性。 1. s...

mickelfeng ⋅ 2013/01/12 ⋅ 1

这是一份全面 & 详细的Google序列化神器protocolBuffer 学习指南

前言 习惯用 数据存储格式的你们,相信大多都没听过 其实 是 出品的一种轻量 & 高效的结构化数据存储格式,性能比 真的强!太!多! 由于 出品,我相信已经具备足够的吸引力 今天,我将献上一...

Carson_Ho ⋅ 05/14 ⋅ 0

PHP多种序列化/反序列化的方法

序列化是将变量转换为可保存或传输的字符串的过程;反序列化就是在适当的时候把这个字符串再转化成原来的变量使用。这两个过程结合起来,可以轻松地存储和传输数据,使程序更具维护性。 1. s...

cnu2m ⋅ 2012/11/08 ⋅ 0

通过 JSON-B 自定义绑定

JSON Binding API 入门,第 2 部分 通过 JSON-B 自定义绑定 通过和注解运行时配置来自定义 JSON 绑定 Alex Theedom 2017 年 12 月 14 日发布 系列内容: 此内容是该系列 2 部分中的第 # 部分...

Alex Theedom ⋅ 2017/12/14 ⋅ 0

JSON Binding API 简介

JSON Binding API 入门,第 1 部分 JSON Binding API 简介 了解 JSON-B 的默认特性和自定义注解、运行时配置等 Alex Theedom 2017 年 12 月 07 日发布 系列内容: 此内容是该系列 1 部分中的...

Alex Theedom ⋅ 2017/12/07 ⋅ 0

Python3中json的操纵

在python中提供了标准库json将基本类型的数据转化成json格式,但是在涉及到自定义类型时需要扩展或者复写Encoder(Decoder)来实现,默认情况下json会抛出"TypeErro: xxx is not json serial...

gangzz ⋅ 2014/02/16 ⋅ 0

没有更多内容

加载失败,请刷新页面

加载更多

下一页

容器之重命名镜像

使用docker tag命令来重命名镜像名称,先执行help,查看如何使用如下 mjduan@mjduandeMacBook-Pro:~/Docker % docker tag --helpUsage:docker tag SOURCE_IMAGE[:TAG] TARGET_IMAGE[:TA...

汉斯-冯-拉特 ⋅ 16分钟前 ⋅ 0

with 的高级用法

那么 上下文管理器 又是什么呢? 上下文管理器协议包含 __enter__ 和 __exit__ 两个方法。with 语句开始运行时,会在上下文管理器对象上调用 __enter__ 方法。with 语句运行结束后,会在上下...

阿豪boy ⋅ 35分钟前 ⋅ 0

使用 jsoup 模拟登录 urp 教务系统

需要的 jsoup 相关 jar包:https://www.lanzous.com/i1abckj 1、首先打开教务系统的登录页面,F12 开启浏览器调试,注意一下 Request Headers 一栏的 Cookie 选项,我们一会需要拿这个 Cook...

大灰狼时间 ⋅ 35分钟前 ⋅ 0

关于线程的创建

转自自己的笔记: http://note.youdao.com/noteshare?id=87584d4874acdeaf4aa027bdc9cb7324&sub=B49E8956E145476191C3FD1E4AB40DFA 1.创建线程的方法 Java使用Thread类代表线程,所有的线程对......

MarinJ_Shao ⋅ 47分钟前 ⋅ 0

工厂模式学习

1. 参考资料 工厂模式-伯乐在线 三种工厂-思否 深入理解工厂模式 2. 知识点理解 2.1 java三种工厂 简单工厂 工厂模式 抽象工厂 2.2 异同点 逐级复杂 简单工厂通过构造时传入的标识来生产产品...

liuyan_lc ⋅ 59分钟前 ⋅ 0

Java NIO

1.目录 Java IO的历史 Java NIO之Channel Java NIO之Buffer Java NIO之Selector Java NIO之文件处理 Java NIO之Charset Java 可扩展IO 2.简介 “IO的历史”讲述了Java IO API从开始到现在的发...

士别三日 ⋅ 今天 ⋅ 0

[Err] ORA-24344: success with compilation error

从txt文本复制出创建function的脚本,直接执行,然后报错:[Err] ORA-24344: success with compilation error。 突然发现脚本的关键字,居然不是高亮显示。 然后我把脚本前面的空格去掉,执行...

wenzhizhon ⋅ 今天 ⋅ 0

Spring Security授权过程

前言 本文是接上一章Spring Security认证过程进一步分析Spring Security用户名密码登录授权是如何实现得; 类图 调试过程 使用debug方式启动https://github.com/longfeizheng/logback该项目,...

hutaishi ⋅ 今天 ⋅ 0

HAProxy基于KeepAlived实现Web高可用及动静分离

前言 软件负载均衡一般通过两种方式来实现: 基于操作系统的软负载实现 基于第三方应用的软负载实现 LVS是基于Linux操作系统实现的一种软负载,而HAProxy则是基于第三方应用实现的软负载。 ...

寰宇01 ⋅ 今天 ⋅ 0

微软自研处理器的小动作:已经开始移植其他平台的工具链

微软将 Windows 10 、Linux 以及工具链如 C/C++ 和 .NET Core 运行时库、Visual C++ 2017 命令行工具、RyuJIT 编辑器等移植到其自主研发的处理器架构 E2。微软还移植了广泛使用的 LLVM C/C++...

linux-tao ⋅ 今天 ⋅ 0

没有更多内容

加载失败,请刷新页面

加载更多

下一页

返回顶部
顶部