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<Book>{
  @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<authorsArray.size();i++){
      authors[i] = authorsArray.get(i).getAsString();
    }
    final Book book = new Book();
    book.setTitle(title);
    book.setAuthors(authors);
    return book;
  }
}

接下来把我们自定义的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<Map<Interger,Author>> mCache
        = new ThreadLocal(){
          protected Map<Integer,Author> 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<Map<Integer,Author>>类型的变量作为Cache,这样可以保证每一个线程都有一个独立的Map<Integer,Author>副本.

所有获取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信息。

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

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 214,128评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,316评论 3 388
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 159,737评论 0 349
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,283评论 1 287
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,384评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,458评论 1 292
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,467评论 3 412
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,251评论 0 269
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,688评论 1 306
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,980评论 2 328
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,155评论 1 342
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,818评论 4 337
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,492评论 3 322
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,142评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,382评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,020评论 2 365
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,044评论 2 352

推荐阅读更多精彩内容