Gson的使用--反序列化

翻译自JavaCreed
首先看一个Json字符串

{
  'title':    'Java Puzzlers: Traps, Pitfalls, and Corner Cases',
  'isbn-10':  '032133678X',
  'isbn-13':  '978-0321336781',
  'authors':  ['Joshua Bloch', 'Neal Gafter']
}

如果我们不使用Gson注解@SerializeName,那怎么来map我们Java bean类中变量的名字
比如我们的java bean类如下

public class Book{
  private String[] authors;
  #@SerializeName("isbn-10")
  #此处我们不使用注解
  private String isbn10;
  #@SerializeName("isbn-13")
  private String isbn13;
  private String title;

  // Methods removed for brevity
}

在具体讲解Json反序列化之前,先了解一下Gson解析中常用的数据结构。JsonElement,JsonPrimitive,JsonObject,JsonArray,JsonNull

还要注意的是,Gson在解析过程中,是把每一个节点都解析成JsonElement,我们在使用的时候需要通过JsonElement的getAsJsonObject等方法把它转换成对应的实际类型。

下面展示一下我们自定义的deserializer,

#实现Gson.Deserializer接口,泛型参数是deserializer方法最终要输出的Java Object
public class BookDeserializer implements Gson.Deserializer{
  @Override
  public Book deserialize(final JsonElement json, 
    final Type typeofT, 
    final JsonDeserializationContext context){
    #Gson会先把输入解析成JsonElement,
    #由于我们的输入是JsonObject,
    #我们需要通过getAsJsonObject进行转换
    JsonObject jsonObject = json.getAsJsonObject();
    #记住我们得到的都是JsonElement对象,要进行转换
    final JsonElement jsonTitle = jsonObject.get("title");
    String tilte = jsonTitle.getAsString();
    fina JsonArray authorsArray =
      jsonObject.get("authors").getAsJsonArray();
    final String[] authors = new String[authorsArray.size()];
    for(int i = 0;i

接下来把我们自定义的BookDeserializer注册给Gson

GsonBuilder builder = new GsonBuilder();
builder.registerTypeAdapter(Book.class, new BookDeserializer());
Gson gson = builder.create();

try(Reader reader = new InputStreamReader(
  this.class.getResourceAsStream("books.json","utf-8"))){
    Book book = gson.fromJson(reader, Book.class);
}

还考虑上面那个例子,当嵌套的时候如何反序列化

  'title':    'Java Puzzlers: Traps, Pitfalls, and Corner Cases',
  'isbn-10':  '032133678X',
  'isbn-13':  '978-0321336781',
  'authors':  [{"id":1,"name":"chico"},{"id":2,"name":"dong"}]

我们这里不采用最简单的使用@SerializeName常规方法,定义一个Author类,然后把Java Bean中的名字和Json中的名字对应起来,所有工作交给Gson自动帮我们做好。

我们仍然要介绍自定义Deserializer的方法,这样可控制性更强,灵活度更高。

首先定义Author类的反序列化器

public class AuthorDeserializer implements JsonDeserialzier{
  public Author deserialize(JsonElement element, Type typeOfT,
    JsonDeserializerContext context){
      JsonObject jsonObject = elemet.getAsJsonObject();
      final int id = jsonObject.get("id").getAsInt();
      final String name = jsonObject.get("name").getAsString();
      Author author = new Author();
      author.setId(id);
      author.setName(name);
      return author;
  }
}

然后在BookDeserializer的deseralize方法中,通过第三个参数JonsDeserializerContext来代理反序列化Author。主要就是增加如下代码

Author[] authors = context.deserialize(jsonObject.get("authors"),Author[].class);

然后在使用的时候需要注册这两个反序列化器

GsonBuilder builder = new GsonBuilder();
builder.registerTypeAdpater(Book.class,new BookDeserializer());
builder.registerTypeAdapter(Author.class, new AuthorDeserializer());

Gson gson = builder.create();

接下来看一下Gson如何解析外键结构,考虑如下的Json String

"authors":[
  {
    "id":1,
    "name":"chico"
  },
  {
    "id":2,
    "name":"dong"
  }
],
"books":[
  {
    "title":"hello",
    "isbn":123456,
    "authors":[1,2]
  }
  {
    "title":"world",
    "isbn":234567,
    "authors":[1]
  }
]

看到这个Json的结构,books包含了authors的id,类似于数据库的外键,这种结构很常见,因为能够有效的较少传输的数据量。

如何解析这种结构呢,提供几种思路:
1.两段式解析,首先按照Json String的结构,解析出来相应的Java类,这里面就是Book类和Author类,但是Book类此时并不包含Author类的引用,只包含Author的id字段。然后进行转换,把Book类映射到Book2类中,Book2这个类中包含了Author类的引用。这个由于需要中间的转换,不推荐

2.另一个是在BookDeserializer类的deserialise方法中传入Author类的信息,这样在反序列化Book类的时候就可以直接得到相应的Author类对象。这种想法看起来很美好,但是实际上需要很大的架构改动才能实现。首先BookDeserializer和AuthorDeserializer需要共享一个Object,这样AuthorDeserializer才能把自己反序列化的结果通知BookDeserializer。而我们在多个Deserializer中间提供通信的是一个JsonDeserializerContext环境变量,这样的话需要Gson的架构有非常大的改动,不推荐

3.第三种方法是让AuthorDeserializer缓存它的解析结果,并且对外提供通过id寻找缓存的Author的方法。这个方法改动最小,而且对外提供方法用到了JsonDeserializerContext,也非常灵活。

先说一下第一种方法的实现思路

按照Json String定义一个数据结构

public class Data{
  Author[] authors;
  Book[] books;

  //提供一些方法,根据Book中包含的id,找到对应的Author对象
  //或者提供一个新类Book2,并提供把Book类转换成Book2的方法
  //Book2这个类包含了Author对象的引用
}

重点说一下第三种实现方法,第三种方法也需要一个Data类来对应Json的结构,不同的是不需要提供根据id来查找Author对象的方法,这个功能通过AuthorDeserializer提供。

接下来重写AuthorDeserializer

public class AuthorDeserializer implements JsonDeserializer{

  private static final ThreadLocal> mCache
        = new ThreadLocal(){
          protected Map initValue(){
            return new HashMap();
          }
        }

  public Author deserialse(JsonElement elememt, Type typeOfT,
    JsonDeserializerContext context) {

      //如果传进来的是id,那么我们直接通过这个Id去寻找对应的Author对象
      if(element.isJsonPrimitive()){
        final JsonPrimitive primitive = element.getAsJsonPrimitive();
        //核心方法,通过id寻找已经缓存的Author对象或者创建Author对应并缓存
        return getOrCreate(primitive.getAsInt());
      }

      //如果传进来的是整个Author的Json String,也去创建或缓存
      if(element.isJsonObject){
        final JsonObject jsonObject = element.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(int id){
    Author author = mCache.get().get(id);
    if(author == null){
      author = new Author();
      author.setId(id);
      mCache.get().put(id,author);
    }
    return author;
  }
}

讲一下改动,我们定义了一个ThreadLocal>类型的变量作为Cache,这样可以保证每一个线程都有一个独立的Map副本.

所有获取Author实例的操作,都由getOrCreate来获得,getOrCreate的巧妙之处在于它仅仅根据id来生成Author,等到真正需要Author的时候,在根据Json String中的内容填入name等字段,这样就不用担心Book和Author哪个先被反序列化了。如果Book先被反序列化,他得到的Author对象是只包含id的Author对象,等Author真正被反序列化的时候剩余的字段会被填上。如果是Author对象先被反序列化,那么直接可以得到完整的Author对象。

然后再看一下deserialize方法的变化,如果我们在BookDeserializer中执行

Author[] authors = context.deserialise(jsonObject.get("authors"),Author[].class);

这样在AuthorDeserializer的deserialize方法中就会走第一个if,也就是

if(element.isJsonPrimitive()){
}

当真正的Author结构的Json String来的时候会走第二个if,也就是

if(element.isJsonObject()){
}

在这里我们根据Json String来补全对应的Author信息。

好了,反序列化最常见的问题已经介绍完了。

你可能感兴趣的:(Gson的使用--反序列化)