【Java】Gson解析复杂数据
来源:互联网 发布:apache 安装版下载 编辑:程序博客网 时间:2024/06/14 05:34
转载来源:https://xesam.github.io/java/2017/02/17/Java-Gson%E8%A7%A3%E6%9E%90%E5%A4%8D%E6%9D%82%E6%95%B0%E6%8D%AE.html
本文主要关注所解析的 JSON 对象与已定义的 java 对象结构不匹配的情况,解决方案就是使用 JsonDeserializer 来自定义从 JSON 对象到 Java 对象的映射。
一个简单的例子
有如下 JSON 对象,表示一本书的基本信息,本书有两个作者。
{ 'title': 'Java Puzzlers: Traps, Pitfalls, and Corner Cases', 'isbn-10': '032133678X', 'isbn-13': '978-0321336781', 'authors': ['Joshua Bloch', 'Neal Gafter']}
这个 JSON 对象包含 4 个字段,其中有一个是数组,这些字段表征了一本书的基本信息。如果我们直接使用 Gson 来解析:
Book book = new Gson().fromJson(jsonString, Book.class);
会发现 ‘isbn-10’ 这种字段表示在 Java 中是不合法的,因为 Java 的变量名中是不允许含有 ‘-‘ 符号的。对于这个例子,我们可以使用 Gson 的 @SerializedName 注解来处理,不过注解也仅限于此,遇到后文的场景,注解就无能为力了。这个时候,就是 JsonDeserializer 派上用场的时候。
假如 Book 类定义如下:
public class Book { private String[] authors; private String isbn10; private String isbn13; private String title; // 其他方法省略}
这个 Book 也定义有 4 个字段,与 JSON 对象的结构基本类似。为了将 JSON 对象解析为 Java 对象,我们需要自定义一个 JsonDeserializer 然后注册到 GsonBuilder 上,并使用 GsonBuilder 来获取解析用的 Gson 对象。
因此, 我们先创建一个 BookDeserializer,这个 BookDeserializer 负责将 Book 对应的 JSON 对象解析为 Java 对象。
public class BookDeserializer implements JsonDeserializer<Book> { @Override public Book deserialize(final JsonElement jsonElement, final Type typeOfT, final JsonDeserializationContext context) throws JsonParseException { //todo 解析字段 final Book book = new Book(); book.setTitle(title); book.setIsbn10(isbn10); book.setIsbn13(isbn13); book.setAuthors(authors); return book; } }
在实现具体的解析之前,我们先来了解一下涉及到的各个类的含义。JsonDeserializer 需要一个 Type,也就是要得到的对象类型,这里当然就是 Book,同时 deserialize() 方法返回的就是 Book 对象。Gson 解析 Json 对象并在内部表示为 JsonElement,一个 JsonElement 可以是如下的任何一种:
- JsonPrimitive :Java 基本类型的包装类,以及 String
- JsonObject:类比 Js 中 Object 的表示,或者 Java 中的 Map<String, JsonElement>,一个键值对结构。
- JsonArray:JsonElement 组成的数组,注意:这里是 JsonElement,说明这个数组是混合类型的。
- JsonNull:值为 null
开始解析(其实这个解析很直观的,Json 里面是什么类型,就按照上面的类型进行对照解析即可),所以, 我们先将 JsonElement 转换为 JsonObject:
// jsonElement 是 deserialize() 的参数 final JsonObject jsonObject = jsonElement.getAsJsonObject();
对照 JSON 定义,然后获取 JsonObject 中的 title。
final JsonObject jsonObject = jsonElement.getAsJsonObject(); JsonElement titleElement = jsonObject.get("title")
我们知道 titleElement 具体是一个字符串(字符串属于 JsonPrimitive),因为可以直接转换:
final JsonObject jsonObject = jsonElement.getAsJsonObject(); JsonElement titleElement = jsonObject.get("title") final String title = jsonTitle.getAsString();
此时我们得到了 title 值,其他类似:
public class BookDeserializer implements JsonDeserializer<Book> { @Override public Book deserialize(JsonElement jsonElement, Type type, JsonDeserializationContext jsonDeserializationContext) throws JsonParseException { final JsonObject jsonObject = jsonElement.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; } }
整体还是很直观的,我们测试一下:
public static void main(String[] args) { GsonBuilder gsonBuilder = new GsonBuilder(); gsonBuilder.registerTypeAdapter(Book.class, new BookDeserializer()); Gson gson = gsonBuilder.create(); Book book = gson.fromJson("{\"title\":\"Java Puzzlers: Traps, Pitfalls, and Corner Cases\",\"isbn-10\":\"032133678X\",\"isbn-13\":\"978-0321336781\",\"authors\":[\"Joshua Bloch\",\"Neal Gafter\"]}", Book.class); System.out.println(book); }
输出结果:
Book{authors=[Joshua Bloch, Neal Gafter], isbn10='032133678X', isbn13='978-0321336781', title='Java Puzzlers: Traps, Pitfalls, and Corner Cases'}
上面,我们使用 GsonBuilder 来注册 BookDeserializer,并创建 Gson 对象。此处得到的 Gson 对象,在遇到需要解析 Book 的时候,就会使用 BookDeserializer 来解析。比如我们使用
gson.fromJson(data, Book.class);
解析的时候,大致流程如下:
- Gson 将输入字符串解析为 JsonElement,同时,这一步也会校验 JSON 的合法性。
- 找到对应的 JsonDeserializer 来解析这个 JsonElement,这里就找到了 BookDeserializer。
- 传入必要的参数并执行 deserialize(),本例中就是在 deserialize() 将一个 JsonElement 转换为 Book 对象。
- 将 deserialize() 的解析结果返回给 fromJson() 的调用者。
对象嵌套
在上面的例子中,一本书的作者都只用了一个名字来表示,但是实际情况中,一个作者可能有很多本书,每个作者实际上还有个唯一的 id 来进行区分。结构如下:
{ 'title': 'Java Puzzlers: Traps, Pitfalls, and Corner Cases', 'isbn': '032133678X', 'authors':[ { 'id': 1, 'name': 'Joshua Bloch' }, { 'id': 2, 'name': 'Neal Gafter' } ]}
此时我们不仅有个 Book 类,还有一个 Author 类:
public class Author{ long id; String name;}
那么问题来了,谁来负责解析这个 authors?有几个选择:
- 我们可以更新 BookDeserializer,同时在其中解析 authors 字段。这种方案耦合了 Book 与 Author 的解析,并不推荐。
- 我们可以使用默认的 Gson 实现,在本例中,Author 类与 author 的 JSON 字符串是一一对应的,因此,这种实现完全没问题。
- 我们还可以实现一个 AuthorDeserializer 来处理 author 字符串的解析问题。
这里我们使用第二种方式,这种方式的改动最小:
JsonDeserializer 的 deserialize() 方法提供了一个 JsonDeserializationContext 对象,这个对象基于 Gson 的默认机制,我们可以选择性的将某些对象的反序列化工作委托给这个JsonDeserializationContext。JsonDeserializationContext 会解析 JsonElement 并返回对应的对象实例:
Author author = jsonDeserializationContext.deserialize(jsonElement, Author.class);
如上例所示,当遇到有 Author 类的解析需求时,jsonDeserializationContext 会去查找用来解析 Author 的 JsonDeserializer,如果有自定义的 JsonDeserializer 被注册过,那么就用自定义的 JsonDeserializer 来解析 Author,如果没有找到自定义的 JsonDeserializer,那就按照 Gson 的默认机制来解析 Author。下面的代码中,我们没有自定义 Author 的 JsonDeserializer,所以 Gson 会自己来处理 authors:
import java.lang.reflect.Type;import com.google.gson.JsonArray;import com.google.gson.JsonDeserializationContext;import com.google.gson.JsonDeserializer;import com.google.gson.JsonElement;import com.google.gson.JsonObject;import com.google.gson.JsonParseException;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 String title = jsonObject.get("title").getAsString(); final String isbn10 = jsonObject.get("isbn-10").getAsString(); final String isbn13 = jsonObject.get("isbn-13").getAsString(); // 委托给 Gson 的 context 来处理 Author[] authors = context.deserialize(jsonObject.get("authors"), Author[].class); final Book book = new Book(); book.setTitle(title); book.setIsbn10(isbn10); book.setIsbn13(isbn13); book.setAuthors(authors); return book; }}
除了上面的方式,我们同样可以自定义一个 ArthurDeserialiser 来解析 Author:
import java.lang.reflect.Type;import com.google.gson.JsonDeserializationContext;import com.google.gson.JsonDeserializer;import com.google.gson.JsonElement;import com.google.gson.JsonObject;import com.google.gson.JsonParseException;public class AuthorDeserializer implements JsonDeserializer { @Override public Author deserialize(final JsonElement json, final Type typeOfT, final JsonDeserializationContext context) throws JsonParseException { final JsonObject jsonObject = json.getAsJsonObject(); final Author author = new Author(); author.setId(jsonObject.get("id").getAsInt()); author.setName(jsonObject.get("name").getAsString()); return author; }}
为了使用 ArthurDeserialiser,同样要用到 GsonBuilder:
import java.io.IOException;import java.io.InputStreamReader;import java.io.Reader;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 BookDeserializer()); gsonBuilder.registerTypeAdapter(Author.class, new AuthorDeserializer()); final Gson gson = gsonBuilder.create(); // Read the JSON data try (Reader reader = new InputStreamReader(Main.class.getResourceAsStream("/part2/sample.json"), "UTF-8")) { // Parse JSON to Java final Book book = gson.fromJson(reader, Book.class); System.out.println(book); } }}
相比委托的方式,自定义 AuthorDeserializer 就根本不需要修改 BookDeserializer 任何代码,Gson 帮你处理了所有的问题。
对象引用
考虑下面的 json 文本:
{ 'authors': [ { 'id': 1, 'name': 'Joshua Bloch' }, { 'id': 2, 'name': 'Neal Gafter' } ], 'books': [ { 'title': 'Java Puzzlers: Traps, Pitfalls, and Corner Cases', 'isbn': '032133678X', 'authors':[1, 2] }, { 'title': 'Effective Java (2nd Edition)', 'isbn': '0321356683', 'authors':[1] } ]}
这个 JSON 对象包含两个 book,两个 author,每本书都通过 author 的 id 关联到 author。因此,每个 book 的 author 值都只是一个 id 而已。这种表示形式在网络响应里面非常常见,通过共用对象减少重复定义来减小响应的大小。
这又给解析 JSON 带来新的挑战,我们需要将 book 和 author 对象组合在一起,但是在解析 JSON文本的时候,Gson 是以类似树遍历的路径来解析的,在解析到 book 的时候,我们只能看到 author 的 id,此时具体的 author 信息却在另一个分支上,在当前的 JsonDeserializationContext 中无法找到所需要的 author。
有几种方法可以处理这个问题:
- 第一个方案,分两段解析。第一段:按照 json 的结构将 json 文本解析成对应的 java 对象,此时,每个 book 对象都包含一个 id 的数组。第二段:直接在得到的 java 对象中,将 author 对象关联到 book 对象。这种方式的有点就是提供了最大的扩展性,不过缺点也是非常明显的,这种方式需要额外的一组辅助 java 类来表示对应的 json 文本结构,最后在转换为满足应用需求的 java 对象(即我们定义的 model)。在本例中,我们的 model 只有两个类: Book 与 Author。但如果使用分段解析的方式,我们还需要额外定义一个辅助 Book 类。在本例中还说得过去,对于那些也有几十上百的 model 来说,那就相当复杂了。
- 另一个方案是向 BookDeserialiser 传递所有的 author 集合,当 BookDeserialiser 在解析到 Author 属性的时候,直接通过 id 从 author 集合中取得对应的 Author 对象。这样就省略了中间步骤以及额外的对象定义。这种方式看起来甚好,不过这要求 BookDeserialiser 和 AuthorDeserialiser 共享同一个对象集合。当获取 author 的时候,BookDeserialiser 需要去访问这个共享对象,而不是像我们先前那样直接从 JsonDeserializationContext 中得到。这样就导致了好几处的改动,包括 BookDeserialiser,AuthorDeserialiser 以及 main() 方法。
- 第三种方案是 AuthorDeserialiser 缓存解析得到的 author 集合,当后面需要通过 id 得到具体 Author 对象的时候,直接返回缓存值。这个方案的好处是通过 JsonDeserializationContext 来实现对象的获取,并且对其他部分都是透明的。缺点就是增加了 AuthorDeserialiser 的复杂度,需要修改 AuthorDeserialiser 来处理缓存。
上述方案都有利有弊,可以针对不同的情况,权衡使用。后文以第三种方案来实现,以避免过多的改动。
Observation
原理上讲,相较于后两种方案,第一种方案提供了更直观的关注点分离,获得中间结果之后,我们可以在外层的 Data 类中实现最后的装配。不过第一种方案的改动太大,这一点前面也说过。我们的目标是做到影响面最小,这也是选用第三种方案的主要原因。
JSON 对象包含两个数组,因此我们需要一个新的类来反映这种结。
public class Data { private Author[] authors; private Book[] books;}
这两个属性的顺序决定了两者的解析顺序,不过在我们的实现中,解析顺序无关紧要,随便哪个属性先解析都可以,具体见后文。
先修改 AuthorDeserialiser 来支持缓存 author 集合:
import java.lang.reflect.Type;import java.util.HashMap;import java.util.Map;import com.google.gson.JsonDeserializationContext;import com.google.gson.JsonDeserializer;import com.google.gson.JsonElement;import com.google.gson.JsonObject;import com.google.gson.JsonParseException;import com.google.gson.JsonPrimitive;public class AuthorDeserializer implements JsonDeserializer<Author> { private final ThreadLocal<Map<Integer, Author>> cache = new ThreadLocal<Map<Integer, Author>>() { @Override protected Map<Integer, Author> initialValue() { return new HashMap<>(); } }; @Override public Author deserialize(final JsonElement json, final Type typeOfT, final JsonDeserializationContext context) throws JsonParseException { // Only the ID is available if (json.isJsonPrimitive()) { final JsonPrimitive primitive = json.getAsJsonPrimitive(); return getOrCreate(primitive.getAsInt()); } // The whole object is available if (json.isJsonObject()) { final JsonObject jsonObject = json.getAsJsonObject(); final Author author = getOrCreate(jsonObject.get("id").getAsInt()); author.setName(jsonObject.get("name").getAsString()); return author; } throw new JsonParseException("Unexpected JSON type: " + json.getClass().getSimpleName()); } private Author getOrCreate(final int id) { Author author = cache.get().get(id); if (author == null) { author = new Author(); author.setId(id); cache.get().put(id, author); } return author; }}
我们来逐一看看:
(1) author 集合缓存在下面的对象中:
private final ThreadLocal<Map<Integer, Author>> cache = new ThreadLocal<Map<Integer, Author>>() { @Override protected Map<Integer, Author> initialValue() { return new HashMap<>(); }};
本实现使用 Map<String, Object> 作为缓存机制,并保存在 ThreadLocal 中,从而进行线程隔离。当然,可是使用其他更好的缓存方案,这个不关键。
(2) 通过下面的方法获取 author :
private Author getOrCreate(final int id) { Author author = cache.get().get(id); if (author == null) { author = new Author(); cache.get().put(id, author); } return author;}
即先通过 id 在缓存中查找 author,如果找到了,就直接为返回对应的 author,否则就根据 id 创建一个空的 author,并加入缓存中,然后返回这个新建的 author。
通过这种方式,我们得以先创建一个吻合 id 的空 author 对象,等到 author 正真可用的时候,再填充缺失的信息。这就是为什么在本例实现中,解析顺序并没有影响的原因,因为缓存对象是共享的。我们可以先解析 book 再解析 author,如果是这种顺序,那么当 book 被解析的时候,其内部的 author 属性只是空有一个 id 的占位对象而已。等到后续解析到 author 的时候,才会真正填充其他信息。
(3) 为了适应新需求,我们修改了 deserialize() 方法。在这个 deserialize() 实现中,其接收的 JsonElement 可能是一个 JsonPrimitive 或者是一个 JsonObject。在 BookDeserialiser 中,碰到解析 author 的时候,传递给 AuthorDeserializer#deserialize() 的就是一个 JsonPrimitive,
// BookDeserialiser 中解析 authors Author[] authors = context.deserialize(jsonObject.get("authors"), Author[].class);
BookDeserialiser 将解析任务委托给 context,context 找到 AuthorDeserializer 来解析这个 Author 数组。此时,AuthorDeserializer#deserialize() 接收到的就是 BookDeserialiser 传递过来的 JsonPrimitive。
另一方面,当解析到 authors 的时候,AuthorDeserializer#deserialize() 接收到的就是 JsonObject。因此,在处理的时候,先做一个类型检测,然互进行恰当的转换:
// 处理 Id 的情况 if (json.isJsonPrimitive()) { final JsonPrimitive primitive = json.getAsJsonPrimitive(); final Author author = getOrCreate(primitive.getAsInt()); return author; }
如果传递进来的是 id, 就先将 JsonElement 转换为 JsonPrimitive 再得到 int。这个 int 就是用来获取 Author 缓存的 id 值。
如果是 JsonObject,就如下转换:
// The whole object is availableif (json.isJsonObject()) { final JsonObject jsonObject = json.getAsJsonObject(); final Author author = getOrCreate(jsonObject.get("id").getAsInt()); author.setName(jsonObject.get("name").getAsString()); return author;}
这一步在返回最终的 author 之前,完成了 author 的填充工作。
如果传递进来的 JsonElement 既不是 JsonPrimitive 也不是 JsonObject,那就说明出错了,中断处理过程。
throw new JsonParseException("Unexpected JSON type: " + json.getClass().getSimpleName());
至此, BookDeserialiser 与 main() 都不需要修改,同时也能满足我们的需求。
Gson 的 desieralizer 是一个强大而灵活的设计,合理运用也可以使我们的设计灵活而容易扩展。
原文地址:http://www.javacreed.com/gson-deserialiser-example/
- 【Java】Gson解析复杂数据
- Gson 复杂数据解析
- Gson解析复杂json数据
- Gson的解析复杂数据
- 使用Gson解析复杂的json数据
- 使用Gson解析复杂的json数据
- 使用Gson解析复杂的json数据
- 使用Gson解析复杂的json数据
- Gson解析复杂的json数据
- Gson解析复杂的json数据
- Android Gson解析多层嵌套复杂数据
- Gson解析复杂的json数据
- Gson解析复杂的json数据
- 使用Gson解析复杂的json数据
- 使用Gson解析复杂的json数据
- Gson解析复杂的json数据
- 使用Gson解析复杂的json数据
- 使用Gson解析复杂的json数据
- Kotlin学习笔记(一)环境配置HelloWorld
- Struts2笔记第一天
- js使用排他思想实现导航栏的切换
- 栈的应用:计算字符串表达式
- Mac OS Git 安装
- 【Java】Gson解析复杂数据
- 算法复习:丑数
- 个人笔记4
- arm-linux 汇总
- 数据结构封装之《LinkQueue链式队列》
- Java websocket入门
- iOS模拟器不能输入中文解决
- chromebook lenovo n21折腾笔记
- 数据结构封装之《SeqQueue顺序式队列》