GSON使用的学习笔记,进阶篇(三)

本篇笔记内容比较杂乱,没有专门去整理。

TypeAdapter

现在轮到TypeAdapter类上场,但考虑到gson默认行为已足够强大,加上项目实践中应用json时场景不会太复杂,所以一般不需要自定义TypeAdapter。TypeAdapter优点是集成了JsonWriter和JsonReader两个类,定义了一套与gson框架交互的良好接口,同时便于管理编码和解码的实现代码,不至于太零碎。因而在了解JsonReader和JsonWriter的使用方法之后,自定义TypeAdapter类来完成特定类的编码和解码也就不困难了。

TypeToken

Type listType = new TypeToken<List<String>>() {}.getType();//!!!
List<String> target = new LinkedList<String>();
target.add("blah");

Gson gson = new Gson();
String json = gson.toJson(target, listType);
List<String> target2 = gson.fromJson(json, listType);// !!!

上述代码在之前的样例里看到过,这里单独拿出来研究。行尾有!!!的两行是这里要特别关注的,原因是看起来很怪异。初次看到第一行代码的时候感觉非常怪异,很奇怪为什么需要设计这样的API。仔细思索之后,发现设计者真实的目的其实非常简单,只是为了提取泛型的类型,原因是Java的泛型对象的类型不能直接通过类型本身获取到,比如类似List<String>.class的代码是无法通过编译,原因和Java泛型的实现机制有关系。

分析TypeToken类的实现代码,发现GSON的开发者想出了一个比较有意思的实现方法,既然不能直接通过类型本身得到真实的类型对象,那么就从对象本身得到。而TypeToken就是这一想法的通用实现。TypeToken类本身不能直接实例化,使用时需要定义其子类对象,这时即通过子类对象来获取其模板类型参数,也就得到了泛型类型的真实类型。

控制缩进

比如面对类似 [{"Qualified Name":"Jackie","age":30,"contact":{"email":"email","phoneno":"phoneno"}},{"Qualified Name":"Jackie Boy","age":1,"contact":{"email":"email","phoneno":"phoneno"}}] 的json格式字符串,对于程序来说理解这样的字符串毫无压力,但对于人来说困难就比较大了。那怎么办呢?gson有办法,通过修改gson的默认行为,可以在输出成json字符串时,提供缩进使用字符串表现出良好的格式。样例代码和缩进后的输出如下。

final Gson gson = new GsonBuilder().setVersion(1.1).setPrettyPrinting().create();// 调用setPrettyPrinting方法,改变gson对象的默认行为
如下即是输出
[
  {
    "Qualified Name": "Jackie",
    "age": 30,
    "contact": {
      "email": "email",
      "phoneno": "phoneno"
    }
  },
  {
    "Qualified Name": "Jackie Boy",
    "age": 1,
    "contact": {
      "email": "email",
      "phoneno": "phoneno"
    }
  }
]

这个特性很有意思,但项目实际开发的时候可能意义不大,毕竟换行、缩进都是占用空间的。日志里输出json格式的字符串时,倒是可以考虑写入调整过格式后的json字符串,在事后分析日志时方便阅读,给运维同事减轻负担。

输出空引用

基于已有的使用经验,如果不做对字段进行特别的处理,对于引用类型的成员,如果取值为null时,编码后将不会出现在json字符串中,这是gson的默认行为。但通过修改gson的默认行为,可以将取值为null的字段也输出到json字符串中。样例代码如下,为了方便查看,还设置了缩进。

final Gson gson = new GsonBuilder().setVersion(1.1).serializeNulls().setPrettyPrinting().create();// 调用serializeNulls方法,改变gson对象的默认行为
如下是输出
[
  {
    "Qualified Name": "Jackie",
    "age": 30,
    "contact": {
      "email": "email",
      "phoneno": "phoneno"
    },
    "address": null,// address和location被原样输出
    "location": null
  },
  {
    "Qualified Name": "Jackie Boy",
    "age": 1,
    "contact": {
      "email": "email",
      "phoneno": "phoneno"
    },
    "address": null,
    "location": null
  }
]

// 如下是类定义
@Data
class Person {
    @Since(1.0)
    @SerializedName("Qualified Name")
    private String name;
    @Since(1.1)
    private int age;
    @Since(1.0)
    private Map<String, Object> contact;

    private String address;
    private String location;

    public Person() {
        contact = new HashMap<String, Object>();
    }

    public void putValue(final String key, final Object value) {
        contact.put(key, value);
    }
}

Gson判断Java Bean的字段是否需要格式化为json的方法

这是一个好问题。有这么几种可能:

  1. 从Java Bean类的元数据中提取成员列表,然后根据各个成员的类型来生成对象的json表示;
  2. 可以提取符合getter/settter方法命名规范的公有方法列表,然后依据这个列表来生成对象的json表示;

设计如下的样例代码,可以检验一下刚才的猜测。

import java.util.HashMap;
import java.util.Map;

import lombok.AccessLevel;
import lombok.Data;
import lombok.Getter;

import com.google.gson.Gson;

public class JsonTest {
    public static void main(final String[] args) {
        final Gson gson = new Gson();

        final Person jack1 = new Person();
        jack1.setAge(30);
        jack1.setName("Jackie");

        jack1.putValue("email", "email");
        jack1.putValue("phoneno", "phoneno");

        final String json = gson.toJson(jack1);
        System.out.println(json);

        final Gson gson2 = new Gson();
        final Person p = gson2.fromJson(json, Person.class);

        System.out.println(p);
    }
}

@Data
class Person {
    private String name;
    @Getter(AccessLevel.NONE)
    // @Setter(AccessLevel.NONE)
    private int age;
    private Map<String, Object> contact;

    private String address;
    private String location;

    public Person() {
        age = 20;
        contact = new HashMap<String, Object>();
    }

    public void putValue(final String key, final Object value) {
        contact.put(key, value);
    }
    /*
    public int getAGe() {
        return age;
    }

    public int getAge() {
        return 90;
    }
    */
}

样例代码的输出如下。

{"name":"Jackie","age":30,"contact":{"email":"email","phoneno":"phoneno"}}
Person(name=Jackie, age=30, contact={email=email, phoneno=phoneno}, address=null, location=null)

前述代码主要是在验证成员的公有方法对gson为对象生成json字符串时的影响。比如通过使用注解,控制lombok不为Person类的成员age生成getter方法,或者写一个命名不合要求的访问方法,或者写一个命名符合要求,但是取值与成员age的值无关的公有getter方法。经验证,这几种修改方法对最终的结果没有任何影响,因而从上面的样例代码可以得出结论,gson在为Bean对象生成json时,并没有依赖类定义中的公有方法。假如gson直接使用Bean元数据中的成员信息来直接生成json的话,暂时没有想到怎样设计样例代码来检验,所以只好单步跟踪gson在生成json时的代码。最终跟踪到了如下的一段代码。

        // 如下代码来自于gson 2.2.4,版权归作者所有
        Field[] fields = raw.getDeclaredFields();
        for (Field field : fields) {
        boolean serialize = excludeField(field, true);
        boolean deserialize = excludeField(field, false);
        if (!serialize && !deserialize) {
          continue;
        }
        field.setAccessible(true);
        Type fieldType = $Gson$Types.resolve(type.getType(), raw, field.getGenericType());
        BoundField boundField = createBoundField(context, field, getFieldName(field),
            TypeToken.get(fieldType), serialize, deserialize);
        BoundField previous = result.put(boundField.name, boundField);
        if (previous != null) {
          throw new IllegalArgumentException(declaredType
              + " declares multiple JSON fields named " + previous.name);
        }
      }

这段代码在类com.google.gson.internal.bind.ReflectiveTypeAdapterFactory的方法getBoundFields中,不相关的部分没有列出来。从这段代码可以看出gson直接读取JavaBean的成员信息来完成json的生成,并没有校验成员是否定义了get/set方法。

GSON使用的学习笔记,进阶篇(三)

时间: 04-30

GSON使用的学习笔记,进阶篇(三)的相关文章

Vue学习笔记进阶篇——Render函数

本文为转载,原文:Vue学习笔记进阶篇--Render函数 基础 Vue 推荐在绝大多数情况下使用 template 来创建你的 HTML.然而在一些场景中,你真的需要 JavaScript 的完全编程的能力,这就是 render 函数,它比 template 更接近编译器. <h1> <a name="hello-world" href="#hello-world"> Hello world! </a> </h1>

Python学习笔记进阶篇——总览

Python学习笔记——进阶篇[第八周]———进程.线程.协程篇(Socket编程进阶&多线程.多进程) Python学习笔记——进阶篇[第八周]———进程.线程.协程篇(异常处理) Python学习笔记——进阶篇[第八周]———进程.线程.协程篇(多线程与进程池) Python学习笔记——进阶篇[第九周]———线程.进程.协程篇(队列Queue和生产者消费者模型) Python学习笔记——进阶篇[第九周]———协程 Python学习笔记——进阶篇[第九周]———MYSQL操作

Vue学习笔记进阶篇——过渡状态

本文为转载,原文:Vue学习笔记进阶篇--过渡状态Vue 的过渡系统提供了非常多简单的方法设置进入.离开和列表的动效.那么对于数据元素本身的动效呢,比如: 数字和运算 颜色的显示 SVG 节点的位置 元素的大小和其他的属性 所有的原始数字都被事先存储起来,可以直接转换到数字.做到这一步,我们就可以结合 Vue 的响应式和组件系统,使用第三方库来实现切换元素的过渡状态. 状态动画和watcher 通过 watcher 我们能监听到任何数值属性的数值更新.可能听起来很抽象,所以让我们先来看看使用 T

Vue学习笔记进阶篇——列表过渡及其他

本文为转载,原文:Vue学习笔记进阶篇--列表过渡及其他本文将介绍Vue中的列表过渡,动态过渡, 以及可复用过渡是实现. 列表过渡 目前为止,关于过渡我们已经讲到: 单个节点 同一时间渲染多个节点中的一个 那么怎么同时渲染整个列表,比如使用 v-for ?在这种场景中,使用 <transition-group>组件.在我们深入例子之前,先了解关于这个组件的几个特点: 不同于 <transition>, 它会以一个真实元素呈现:默认为一个<span>.你也可以通过 tag

Vue学习笔记进阶篇——多元素及多组件过渡

本文为转载,原文:Vue学习笔记进阶篇--多元素及多组件过渡 多元素的过渡 对于原生标签可以使用 v-if/v-else.但是有一点需要注意: 当有相同标签名的元素切换时,需要通过 key 特性设置唯一的值来标记以让 Vue 区分它们,否则 Vue 为了效率只会替换相同标签内部的内容.即使在技术上没有必要,给在 <transition> 组件中的多个元素设置 key 是一个更好的实践. 示例: <transition> <button v-if="isEditing

Python学习笔记——进阶篇【第八周】———FTP断点续传作业&amp;批量主机管理工具

主机管理之paramiko模块学习 http://www.cnblogs.com/wupeiqi/articles/5095821.html 作业1:用socketserver继续完善FTP作业 作业2:开发一个批量主机管理工具 需求: 可以对机器进行分组 可以对指定的一组或多组机器执行批量命令,分发文件(发送\接收) 纪录操作日志 作业参考链接http://www.cnblogs.com/alex3714/articles/5227251.html

Python学习笔记——进阶篇【第九周】———线程、进程、协程篇(队列Queue和生产者消费者模型)

Python之路,进程.线程.协程篇 本节内容 进程.与线程区别 cpu运行原理 python GIL全局解释器锁 线程 语法 join 线程锁之Lock\Rlock\信号量 将线程变为守护进程 Event事件 queue队列 生产者消费者模型 Queue队列 开发一个线程池 进程 语法 进程间通讯 进程池 参考链接http://www.cnblogs.com/alex3714/articles/5230609.html

Python学习笔记——进阶篇【第八周】———异常处理

  引用链接:http://www.cnblogs.com/wupeiqi/articles/5017742.html isinstance(obj,cls) #判断实例是不是在类里 #类似代码 a=[1,2,3] if type(a) is list:print(a) 简便方法: class Foo(object): pass obj = Foo() isinstance(obj,Foo)  issubclass(sub,super) #检查sub是不是super的子类(很少用) class

Python学习笔记——进阶篇【第八周】———Socket编程进阶&amp;多线程、多进程

本节内容: 异常处理 Socket语法及相关 SocketServer实现多并发 进程.线程介绍 threading实例 线程锁.GIL.Event.信号量 生产者消费者模型 红绿灯.吃包子实例 multiprocess实例 进程间通讯 队列Queue 原文目录链接:http://www.cnblogs.com/alex3714/articles/5227251.html 作业1:用socketserver继续完善FTP作业 作业2:开发一个批量主机管理工具 需求: 可以对机器进行分组 可以对指

Python学习笔记——进阶篇【第八周】———CPU运行原理与多线程

CPU运行原理与多线程 什么是线程(thread)? 线程是操作系统能够进行运算调度的最小单位.它被包含在进程之中,是进程中的实际运作单位.一条线程指的是进程中一个单一顺序的控制流,一个进程中可以并发多个线程,每条线程并行执行不同的任务 进程是容器,线程是真正执行的任务单元