• 完全理解Gson(2):Gson序列化


    通过调用 Gson API 可以把 Java 对象转换为 JSON 格式的字符串(项目主页)。在这篇文章中,我们将会讲到如何通过 Gson 默认实现和自定义实现方式,将 Java  对象转换为 JSON 字符串。

    对于那些不熟悉 Gson 的读者,建议在读本篇文章之前读一下这两篇文章:简单 Gson 实例和 Gson 反序列化实例。另外,这篇文章的讲述方式和Gson反序列化实例一样,并且使用了相同的例子。

    注意

    请注意,在文章中我们将互换格式化或序列化的术语。

    下面列出的所有代码都可以在这里找到: http://java-creed-examples.googlecode.com/svn/gson/Gson Serialiser Example/ 。大多数例子不会包含完整的代码,可能会忽略和要讨论的例子不相关的片段。读者可以从上面的链接下载查看完整的代码。

    简单的例子

    考虑下面这个 Java 对象。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    package com.javacreed.examples.gson.part1;
     
    public class Book {
     
      private String[] authors;
      private String isbn10;
      private String isbn13;
      private String title;
     
      // Methods removed for brevity
     
    }

    这个简单的 Java 类封装了一本书的属性。假如我们需要将其序列化为下面这个 JSON 对象。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    {
      "title": "Java Puzzlers: Traps, Pitfalls, and Corner Cases",
      "isbn-10": "032133678X",
      "isbn-13": "978-0321336781",
      "authors": [
        "Joshua Bloch",
        "Neal Gafter"
      ]
    }

    Gson 不需要任何特殊配置就可以序列化 Book 类。Gson 使用 Java 字段名称作为 JSON 字段的名称,并赋予对应的值。如果仔细地看一下上面的那个 JSON 示例会发现, ISBN 字段包含一个减号:isbn-10 和 isbn-13。不幸的是,使用默认配置不能将这些字段包含进来。解决问题的办法之一就是使用注解,就像在这篇文章中描述的那样:Gson 注解示例。使用注解可以自定义 JSON 字段的名称,Gson将会以注解为准进行序列化。另一个方法就是使用 JsonSerialiser (Java Doc),如下所示:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    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 BookSerialiser implements JsonSerializer {
     
        @Override
        public JsonElement serialize(final Book book, final Type typeOfSrc, final JsonSerializationContext context) {
            final JsonObject jsonObject = new JsonObject();
            //The serialisation code is missing
     
            return jsonObject;
        }
    }

    上面的例子还缺失了重要部分,需要通过补充序列化代码来完善。在添加更多代码使其变得复杂之前,我们先来理解下这个类。

    JsonSerializer 接口要求类型是将要进行序列化的对象类型。在这个例子中,我们要序列化的 Java 对象是 Book。serialize()方法的返回类型必须是一个 JsonElement (Java 文档)类型的实例。详见这篇文章:Gson 反序列化实例,下面列出了JsonElement 四种具体实现类型:

    • JsonPrimitive (Java Doc) —— 例如一个字符串或整型
    • JsonObject (Java Doc) —— 一个以 JsonElement 名字(类型为 String)作为索引的集合。类似于 Map<String,JsonElement>集合(Java Doc
    • JsonArray (Java Doc)—— JsonElement 的集合。注意数组的元素可以是四种类型中的任意一种,或者混合类型都支持。
    • JsonNull (Java Doc) —— 值为null

    JsonElement的类型

    上面这张图片展示了 JsonElement 的所有类型。可以把 JsonObject 看作值为 JsonElement 的键值对集合。因此,这些值可以是另外四种对象。

    下面是序列化的完整实例:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    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 BookSerialiser implements JsonSerializer {
     
        @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;
        }
    }

    我们在这里添加了一些代码。在理解整个图片的含义前,我们先把它拆成一个个小的部分,先来解释下每部分的含义。

    如果要序列化这个 Java 对象,首先需要创建一个 JsonElement 实例。例子中是返回了一个 JsonObject 实例来代表 Book 对象,如下所示:

    1
    final JsonObject jsonObject = new JsonObject();

    该对象使用我们设置的字段名称进行填充,如下:

    1
    2
    3
    4
    5
    // The variable 'book' is passed as a parameter to the serialize() method
        final JsonObject jsonObject = new JsonObject();
        jsonObject.addProperty("title", book.getTitle());
        jsonObject.addProperty("isbn-10", book.getIsbn10());
        jsonObject.addProperty("isbn-13", book.getIsbn13());

    使用 addProperty() 方法 (Java Doc)可以添加任何 Java 原始类型以及String和Number。注意此处的 name 必须是唯一的,否则会被前一个覆盖掉。可以将其看做是一个将字段名作为值索引的 Map。

    更复杂的对象,比如 Java 对象或数组就不能使用上面的方法来添加了。JsonObject 有另外一个 add() 方法,可以用来作为替代,如下所示:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    // The variable 'book' is passed as a parameter to the serialize() method
        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);

    首先创建一个 JsonArray 对象,然后将所有 authors 添加进去。和 Java 不同的是,初始化 JsonArray 时不需要指定数组的大小。事实上,抛开这个类的名字不看,可以将 JsonArray 类更多地看做是一个 list 而非 array。最后将 jsonAuthorsArray  添加到 jsonObject 中。此处也可以在给 jsonAuthorsArray 添加元素之前,将其添加到 jsonObject 中。

    在调用该序列化方法之前,我们需要将其注册到 Gson 中:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    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 {
        // Configure GSON
        final GsonBuilder gsonBuilder = new GsonBuilder();
        gsonBuilder.registerTypeAdapter(Book.class, new BookSerialiser());
        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);
      }
    }

    通过注册我们自己实现的序列化器,告诉 Gson 无论什么时候序列化 Book 类型的对象都使用该序列化器进行序列化。

    在上面例子中,通过调用 set prettying printing 方法还告诉了 Gson 对生成的 JSON 对象进行格式化,如下所示:

    1
    gsonBuilder.setPrettyPrinting();

    虽然这对于调试和教程非常有用,但请不要在生产环境中这样用,因为可能会因此产生更大的 JSON 对象(文本的大小)。除此之外,由于 Gson 必须要格式化 JSON 对象,即对其进行相应的缩进,pretty printing 会有一些性能方面的消耗。

    运行上面的代码可以得到预期的 JSON 对象。对我们的第一个例子做个总结,即怎样自定义 Gson 序列化器将 Java 对象序列化为 JSON 对象。下一章将会讲到怎样使用 Gson 序列化嵌套对象。

    嵌套对象

    接下来的例子将会描述怎么序列化嵌套对象。所谓嵌套对象是指在其它对象内部的对象。在此我们将会引入一个新的实体:author。形成了这样一个包含 title 和 ISBN 连同 author 列表的 book。在这个例子中将会得到一个包含新实体的 JSON 对象,与前面的JSON对象不同,就像下面那样:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    {
      "title": "Java Puzzlers: Traps, Pitfalls, and Corner Cases",
      "isbn": "032133678X",
      "authors": [
        {
          "id": 1,
          "name": "Joshua Bloch"
        },
        {
          "id": 2,
          "name": "Neal Gafter"
        }
      ]
    }

    注意,前一个例子中 authors 只是一个简单的字符数组:

    1
    2
    3
    4
    "authors": [
        "Joshua Bloch",
        "Neal Gafter"
      ]

    这个例子中的 authors 是一个 JSON 对象,而不仅仅只是一个基本类型。

    1
    2
    3
    4
    {
          "id": 1,
          "name": "Joshua Bloch"
        }

    author 的 JSON对象有一个 id 和一个 name字段。下面是 Author 类。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    package com.javacreed.examples.gson.part2;
     
    public class Author {
     
      private int id;
      private String name;
     
      // Methods removed for brevity
     
    }

    这个类非常简单,由两个字段组成,并且都是原始类型。Book 类被修改为使用 Author 类,如下所示:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    package com.javacreed.examples.gson.part2;
     
    public class Book {
     
      private Author[] authors;
      private String isbn;
      private String title;
     
      // Methods removed for brevity
     
    }

    author 字段从一个 integer 数组变成了一个 Author 数组。因此必须修改下 BookSerialiser 类来兼容这一改变,如下:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    package com.javacreed.examples.gson.part2;
     
    import java.lang.reflect.Type;
     
    import com.google.gson.JsonElement;
    import com.google.gson.JsonObject;
    import com.google.gson.JsonSerializationContext;
    import com.google.gson.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", book.getIsbn());
     
        final JsonElement jsonAuthros = context.serialize(book.getAuthors());
        jsonObject.add("authors", jsonAuthros);
     
        return jsonObject;
      }
    }

    authors 的序列化由 context(作为 serialize() 方法的一个参数被传进来,是 JsonSerializationContext 的实例) 来完成。context 将会序列化给出的对象,并返回一个 JsonElement。同时 context 也会尝试找到一个可以序列化当前对象的序列化器。如果没有找到,其将会使用默认的序列化器。目前,我们还不会为 Author 类实现一个序列化器,仍然会使用默认实现作为替代。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    package com.javacreed.examples.gson.part2;
     
    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 {
        // Configure GSON
        final GsonBuilder gsonBuilder = new GsonBuilder();
        gsonBuilder.registerTypeAdapter(Book.class, new BookSerialiser());
        gsonBuilder.setPrettyPrinting();
        final Gson gson = gsonBuilder.create();
     
        final Author joshuaBloch = new Author();
        joshuaBloch.setId(1);
        joshuaBloch.setName("Joshua Bloch");
     
        final Author nealGafter = new Author();
        nealGafter.setId(2);
        nealGafter.setName("Neal Gafter");
     
        final Book javaPuzzlers = new Book();
        javaPuzzlers.setTitle("Java Puzzlers: Traps, Pitfalls, and Corner Cases");
        javaPuzzlers.setIsbn("032133678X");
        javaPuzzlers.setAuthors(new Author[] { joshuaBloch, nealGafter });
     
        final String json = gson.toJson(javaPuzzlers);
        System.out.println(json);
      }
    }

    上面的例子创建并配置 Gson 使用自定义的 BookSerialiser 进行序列化。我们创建了两个 author 对象和一个 book 对象,并序列化该 book 对象。这样将会得到在章节开始时展示的 JSON 对象。

    完整起见,下面是一个 Author 类的序列化器:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    package com.javacreed.examples.gson.part2;
     
    import java.lang.reflect.Type;
     
    import com.google.gson.JsonElement;
    import com.google.gson.JsonObject;
    import com.google.gson.JsonSerializationContext;
    import com.google.gson.JsonSerializer;
     
    public class AuthorSerialiser implements JsonSerializer<Author> {
     
      @Override
      public JsonElement serialize(final Author author, final Type typeOfSrc, final JsonSerializationContext context) {
        final JsonObject jsonObject = new JsonObject();
        jsonObject.addProperty("id", author.getId());
        jsonObject.addProperty("name", author.getName());
     
        return jsonObject;
      }
    }

    上面的序列化器并没有引入什么新的特性,因此不需要再多做解释。如果想要使用这个新的序列器,需要将其注册到 GsonBuilder (Java Doc) 中。

    本章对嵌套对象的序列化做了总结。可以将嵌套对象的序列化交给 context 处理,其在序列化时会顺带尝试找到一个合适的序列化器,并返回相应的 JsonElement。下一章也是最后一章将会讲如何处理对象引用的序列化。

    对象引用

    一个对象对其它对象的引用叫做对象引用,book 和 author 类之间的关系就是这样。同一个 author 可以有多本 book。例如 author Joshua Bloch(Author at Amazon)不止一本 book。在使用序列化器描述之前,我们将会清除重复的 author。

    考虑下面这个例子:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    package com.javacreed.examples.gson.part3;
     
    import java.io.IOException;
     
    import com.google.gson.Gson;
    import com.google.gson.GsonBuilder;
     
    public class Example1 {
     
      public static void main(final String[] args) throws IOException {
        // Configure GSON
        final GsonBuilder gsonBuilder = new GsonBuilder();
        gsonBuilder.setPrettyPrinting();
        final Gson gson = gsonBuilder.create();
     
        final Author joshuaBloch = new Author();
        joshuaBloch.setId(1);
        joshuaBloch.setName("Joshua Bloch");
     
        final Author nealGafter = new Author();
        nealGafter.setId(2);
        nealGafter.setName("Neal Gafter");
     
        final Book javaPuzzlers = new Book();
        javaPuzzlers.setTitle("Java Puzzlers: Traps, Pitfalls, and Corner Cases");
        javaPuzzlers.setIsbn("032133678X");
        javaPuzzlers.setAuthors(new Author[] { joshuaBloch, nealGafter });
     
        final Book effectiveJava = new Book();
        effectiveJava.setTitle("<span class="wp_keywordlink"><a href="http://www.amazon.com/gp/product/B000WJOUPA/ref=as_li_qf_sp_asin_il_tl?ie=UTF8&camp=1789&creative=9325&creativeASIN=B000WJOUPA&linkCode=as2&tag=job0ae-20" title="Effective Java" rel="nofollow" target="_blank" class="external">Effective Java</a></span> (2nd Edition)");
        effectiveJava.setIsbn("0321356683");
        effectiveJava.setAuthors(new Author[] { joshuaBloch });
     
        final Book[] books = new Book[] { javaPuzzlers, effectiveJava };
     
        final String json = gson.toJson(books);
        System.out.println(json);
      }
    }

    两个作者和两本书,其中一个作者在两本书中都有。注意,有两个 author 对象而不是三个,代表 Joshua Bloch 的实例被两个 book 对象共享。最后注意,我们故意没有使用任何自定义的序列化器。下面是执行上面代码的结果。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    [
      {
        "authors": [
          {
            "id": 1,
            "name": "Joshua Bloch"
          },
          {
            "id": 2,
            "name": "Neal Gafter"
          }
        ],
        "isbn": "032133678X",
        "title": "Java Puzzlers: Traps, Pitfalls, and Corner Cases"
      },
      {
        "authors": [
          {
            "id": 1,
            "name": "Joshua Bloch"
          }
        ],
        "isbn": "0321356683",
        "title": "Effective Java (2nd Edition)"
      }
    ]

    得到了两个 book 类型的 JSON 和三个 author 类型的 JSON。而对于author:Joshua Bloch 是重复的。

    1
    2
    3
    4
    {
           "id": 1,
           "name": "Joshua Bloch"
         }

    这会使得 JSON 对象明显变大,尤其是对更复杂的对象。理想情况下,book 的 JSON 对象应该只包含 author 的 id 而不是整个对象。这类似于关系型数据库中 book 表有一个外键关联到 author 表。

    Book 类将会被修改为包含一个只返回 author 的 id 的方法,如下:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    package com.javacreed.examples.gson.part3;
     
    public class Book {
     
      private Author[] authors;
      private String isbn;
      private String title;
     
      public Author[] getAuthors() {
        return authors;
      }
     
      public int[] getAuthorsIds() {
        final int[] ids = new int[authors.length];
        for (int i = 0; i < ids.length; i++) {
          ids[i] = authors[i].getId();
        }
        return ids;
      }// Other methods removed for brevity
     
    }

    当序列化 book 时,使用 author 的 id 代替整个 author 对象,如下:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    package com.javacreed.examples.gson.part3;
     
    import java.lang.reflect.Type;
     
    import com.google.gson.JsonElement;
    import com.google.gson.JsonObject;
    import com.google.gson.JsonSerializationContext;
    import com.google.gson.JsonSerializer;
     
    public class BookSerialiser implements JsonSerializer {
     
      @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", book.getIsbn());
     
        final JsonElement jsonAuthros = context.serialize(book.getAuthorsIds());
        jsonObject.add("authors", jsonAuthros);
     
        return jsonObject;
      }
    }

    下面是使用这个序列化器得到的结果:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    [
      {
        "title": "Java Puzzlers: Traps, Pitfalls, and Corner Cases",
        "isbn": "032133678X",
        "authors": [
          1,
          2
        ]
      },
      {
        "title": "Effective Java (2nd Edition)",
        "isbn": "0321356683",
        "authors": [
          1
        ]
      }
    ]

    现在我们使用 author 的 id 代替了整个 author。这一做法使得其比前一个有更小的空间占用。对于更大的对象将产生巨大的影响。

    这个例子是整篇文章关于序列化的总结。从中我们学会了怎么使用默认或自定义的序列化选项,将 Java 对象序列化为 JSON 字符串。

    原文链接: javacreed 翻译: ImportNew.com 飘扬叶
    译文链接: http://www.importnew.com/16638.html

    from: http://www.importnew.com/16638.html

  • 相关阅读:
    BZOJ1051 [HAOI2006]受欢迎的牛 强连通分量缩点
    This blog has been cancelled for a long time
    欧拉定理、费马小定理及其拓展应用
    同余基础
    [LeetCode] 73. Set Matrix Zeroes
    [LeetCode] 42. Trapping Rain Water
    [LeetCode] 41. First Missing Positive
    [LeetCode] 71. Simplify Path
    [LeetCode] 148. Sort List
    [LeetCode] 239. Sliding Window Maximum
  • 原文地址:https://www.cnblogs.com/GarfieldEr007/p/6821672.html
Copyright © 2020-2023  润新知