美文网首页
Gson的使用--反序列化

Gson的使用--反序列化

作者: 董成鹏 | 来源:发表于2018-07-03 15:36 被阅读0次

    翻译自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信息。

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

    相关文章

      网友评论

          本文标题:Gson的使用--反序列化

          本文链接:https://www.haomeiwen.com/subject/vmlzeftx.html