Gson(又称Google Gson)是Google公司发布的一个开放源代码的Java库,主要用途为序列化Java对象为JSON字符串,或反序列化JSON字符串成Java对象。

一、Gson的基本用法

POJO类与JSON转换

@Data
public class Sku implements Serializable {
    /** id 编号 */
    private long skuId;
    /** 图片地址 */
    private String imgUrl;
    /** 创建时间 */
    private Date createTime;
}

生成JSON

Sku sku = new Sku();
sku.setSkuId(20190317001L);
sku.setImgUrl("//img12.360buyimg.com/45ab3dd6c35d981b.jpg");
sku.setCreateTime(new Date());

Gson gson = new Gson();
String json = gson.toJson(sku);
// {"skuId":20190317001,"imgUrl":"//img12.360buyimg.com/45ab3dd6c35d981b.jpg","createTime":"Mar 17, 2019 11:23:20 AM"}

解析JSON:

Gson gson = new Gson();
String jsonString = "{\"skuId\":20190317001,\"imgUrl\":\"//img12.360buyimg.com/45ab3dd6c35d981b.jpg\",\"createTime\":\"Mar 17, 2019 11:23:20 AM\"}";
Sku skuFromJson = gson.fromJson(jsonString, Sku.class);

属性重命名 @SerializedName

接收数据时期望json格式:

{"skuId":20190317001,"imgUrl":"//img12.360buyimg.com/45ab3dd6c35d981b.jpg"}

实际json格式:

{"skuId":20190317001,"img_url":"//img12.360buyimg.com/45ab3dd6c35d981b.jpg"}

前台和后台(团队之间)数据命名格式不统一,java后台采用驼峰方法命名,前台js可能钟情于下划线方式命名;团队之间对于同一个字段的命名方式不同。这种情况下双方不肯妥协或是历史遗留问题,怎么办?
使用@SerializedName注解对属性重命名:

/** 图片地址 */
@SerializedName("img_url")
private String imgUrl;

上面的问题解决了
但是由于设计不严谨,又有团队发来以下格式的json,怎么办?

{"skuId":20190317001,"img":"//img12.360buyimg.com/45ab3dd6c35d981b.jpg"}
{"skuId":20190317001,"imgUrl":"//img12.360buyimg.com/45ab3dd6c35d981b.jpg"}

我们可以为POJO字段提供备选属性名
SerializedName注解提供了两个属性,上面用到了一个,还有一个属性alternate,接收一个String数组。

@SerializedName(value = "imgUrl", alternate = {"img", "img_url"})
private String imgUrl;

二、Gson泛型支持

解析Sku对象数组json串,如下:

[{"skuId":1,"imgUrl":"//img.com/1.jpg"},{"skuId":2,"imgUrl":"//img.com/2.jpg"}]

Gson提供了TypeToken来实现对泛型的支持,所以当我们希望使用将以上的数据解析为List时需要这样写。

Gson gson = new Gson();
String skuListJson = "[{\"skuId\":1,\"imgUrl\":\"//img.com/1.jpg\"},{\"skuId\":2,\"imgUrl\":\"//img.com/2.jpg\"}]";
List<Sku> resultSkuList = gson.fromJson(skuListJson, new TypeToken<List<Sku>>(){}.getType());

注:TypeToken的构造方法是protected修饰的,所以上面才会写成new TypeToken<List>() {}.getType() 而不是 new TypeToken<List>().getType()

三、GsonBuilder介绍

一般情况下Gson类提供的 API已经能满足大部分的使用场景,但我们需要更特殊、更强大的功能时,这时候就引入一个新的类 GsonBuilder。

GsonBuilder是用于构建Gson实例的一个类,要想改变Gson默认的设置必须使用该类配置Gson。

基本用法

Gson gson = new GsonBuilder()
                // 各种配置
                .create(); //生成配置好的Gson

Gson在默认情况下是不动导出值null的键的,如:

@Data
public class Sku implements Serializable {
    /** id 编号 */
    private long skuId;
    /** 图片地址 */
    private String imgUrl;
    /** 创建时间 */
    private Date createTime;
}

@Test
public void testToJson() {
	Sku sku = new Sku();
	sku.setSkuId(20190317001L);
	sku.setCreateTime(new Date());
	Gson gson = new Gson();
	String json = gson.toJson(sku);
	log.info(json); // {"skuId":20190317001,"createTime":"Mar 17, 2019 1:11:33 PM"}
}

可以看出,imgUrl字段在json串中没有出现,如果接口要求没有值必须用null代替时,怎么处理?
如下:

@Test
public void testToJson() {
	Sku sku = new Sku();
	sku.setSkuId(20190317001L);
	sku.setCreateTime(new Date());
	Gson gson = new GsonBuilder()
			// 序列化空值
			.serializeNulls()
			.create();
	String json = gson.toJson(sku);
	log.info(json); // {"skuId":20190317001,"imgUrl":null,"createTime":"Mar 17, 2019 1:15:06 PM"}
}

POJO与JSON的字段映射规则:

GsonBuilder.setFieldNamingPolicy 方法与Gson提供枚举类FieldNamingPolicy配合使用,该枚举类提供了5种实现方式分别为:

FieldNamingPolicy 结果
IDENTITY {"skuId":20190317001,"createTime":"2019-03-17"}
LOWER_CASE_WITH_DASHES {"sku-id":20190317001,"create-time":"2019-03-17"}
LOWER_CASE_WITH_UNDERSCORES {"sku_id":20190317001,"create_time":"2019-03-17"}
UPPER_CAMEL_CASE {"SkuId":20190317001,"CreateTime":"2019-03-17"}
UPPER_CAMEL_CASE_WITH_SPACES {"Sku Id":20190317001,"Create Time":"2019-03-17"}
Gson gson = new GsonBuilder()
		.setFieldNamingPolicy(FieldNamingPolicy.IDENTITY)
		.create();

自定义映射规则:

Gson gson = new GsonBuilder()
        .setFieldNamingStrategy(new FieldNamingStrategy() {
            @Override
            public String translateName(Field f) {
                // 实现自己的规则,规则定义可参照FieldNamingPolicy枚举类书写
                return null;
            }
        })
        .create();

格式化输出、日期时间及其它:

Gson gson = new GsonBuilder()
        // 序列化null
        .serializeNulls()
        // 设置日期时间格式,另有2个重载方法
        // 在序列化和反序化时均生效
        .setDateFormat("yyyy-MM-dd")
        // 禁此序列化内部类
        .disableInnerClassSerialization()
        // 生成不可执行的Json(多了 )]}' 这4个字符)
        .generateNonExecutableJson()
        // 禁止转义html标签
        .disableHtmlEscaping()
        // 格式化输出
        .setPrettyPrinting()
        .create();

四、过滤字段的几种方式

基于@Expose注解

@Expose 注解从名字上就可以看出是暴露的意思,所以该注解是用于对外暴露字段的。该注解必须和GsonBuilder配合使用,默认通过new Gson()的方式该注解不生效。

@Expose提供了两个属性,且都有默认值,开发者可以根据需要设置不同的值。

@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.FIELD})
public @interface Expose {
    boolean serialize() default true;

    boolean deserialize() default true;
}

使用方法: 简单说来就是需要导出的字段上加上**@Expose 注解,不导出的字段不加。注意是不导出的不加**。

@Expose // 默认序列化反序列化都生效
@Expose(deserialize = true,serialize = true) // 序列化和反序列化都都生效,等价于上一条
@Expose(deserialize = true,serialize = false) // 反序列化时生效
@Expose(deserialize = false,serialize = true) // 序列化时生效
@Expose(deserialize = false,serialize = false) // 和不写注解一样

Gson gson = new GsonBuilder()
        .excludeFieldsWithoutExposeAnnotation()
        .create();

基于版本

Gson在对基于版本的字段导出提供了两个注解 @Since@Until,和 GsonBuilder.setVersion(Double) 配合使用。@Since 和 @Until都接收一个Double值。

使用方法:当前版本(GsonBuilder中设置的版本) 大于等于Since的值时该字段导出,小于Until的值时该该字段导出。

class SinceUntilSample {
    @Since(4)
    public String since;
    @Until(5)
    public String until;
}

public void sineUtilTest(double version){
        SinceUntilSample sinceUntilSample = new SinceUntilSample();
        sinceUntilSample.since = "since";
        sinceUntilSample.until = "until";
        Gson gson = new GsonBuilder().setVersion(version).create();
        System.out.println(gson.toJson(sinceUntilSample));
}
// 当version <4时,结果:{"until":"until"}
// 当version >=4 && version <5时,结果:{"since":"since","until":"until"}
// 当version >=5时,结果:{"since":"since"}

注:当一个字段被同时注解时,需两者同时满足条件。

基于访问修饰符

什么是修饰符? public、static 、final、private、protected 这些就是,使用方式:

class ModifierSample {
    final String finalField = "final";
    static String staticField = "static";
    public String publicField = "public";
    protected String protectedField = "protected";
    String defaultField = "default";
    private String privateField = "private";
}

ModifierSample modifierSample = new ModifierSample();
Gson gson = new GsonBuilder()
        .excludeFieldsWithModifiers(Modifier.FINAL, Modifier.STATIC, Modifier.PRIVATE)
        .create();
log.info(gson.toJson(modifierSample)); 
// {"publicField":"public","protectedField":"protected","defaultField":"default"}

基于策略(自定义规则)

基于策略是利用Gson提供的ExclusionStrategy接口,同样需要使用GsonBuilder,相关API 2个,分别是addSerializationExclusionStrategy 和addDeserializationExclusionStrategy 分别针对序列化和反序化时。这里以序列化为例。

Gson gson = new GsonBuilder()
        .addSerializationExclusionStrategy(new ExclusionStrategy() {
            @Override
            public boolean shouldSkipField(FieldAttributes f) {
                // 按属性名排除
                if ("skuId".equals(f.getName())){
                    return true;
                }
                Expose expose = f.getAnnotation(Expose.class);
                // 按注解排除
                if (expose != null && !expose.deserialize()){
                    return true;
                } 
                return false;
            }
            @Override
            public boolean shouldSkipClass(Class<?> clazz) {
                // 直接排除某个类 ,return true为排除
                return (clazz == int.class || clazz == Integer.class);
            }
        })
        .create();

是不是很强大,想怎么排除就怎么排除。

五、TypeAdapter

TypeAdapter 是Gson提供的一个抽象类,用于接管某种类型的序列化和反序列化过程,包含两个注要方法 write(JsonWriter,T) 和 read(JsonReader) 。

/**
 * @author jiangjian
 * @date 2019/3/17 14:40
 */
public class SkuAdapter extends TypeAdapter<Sku> {
    @Override
    public void write(JsonWriter jsonWriter, Sku sku) throws IOException {
        jsonWriter.beginObject();
        jsonWriter.name("SKU编码").value(sku.getSkuId());
        jsonWriter.name("图片地址").value(sku.getImgUrl());
        jsonWriter.name("创建时间").value(DateFormatUtils.format(sku.getCreateTime(), "yyyy-MM-dd"));
        jsonWriter.endObject();
    }

    @Override
    public Sku read(JsonReader jsonReader) throws IOException {
        Sku sku = new Sku();
        jsonReader.beginObject();
        while (jsonReader.hasNext()) {
            switch (jsonReader.nextName()) {
                case "skuId":
                    sku.setSkuId(jsonReader.nextLong());
                    break;
                case "img":
                case "img_url":
                case "imgUrl":
                    sku.setImgUrl(jsonReader.nextString());
                    break;
                case "createTime":
                    try {
                        sku.setCreateTime(DateUtils.parseDate(jsonReader.nextString(),"yyyy-MM-dd"));
                    } catch (ParseException e) {
                        sku.setCreateTime(null);
                    }
                    break;
                default:
            }
        }
        jsonReader.endObject();
        return sku;
    }
}

@Data
@JsonAdapter(SkuAdapter.class)
public class Sku implements Serializable {
    /** id 编号 */
    private long skuId;

    /** 图片地址 */
    @Expose
    private String imgUrl;

    /** 创建时间 */
    private Date createTime;
}

@Test
public void testToJson() {
	Sku sku = new Sku();
	sku.setSkuId(20190317001L);
	sku.setImgUrl("//img.com/xx.jpg");
	sku.setCreateTime(new Date());
	Gson gson = new Gson();
	String json = gson.toJson(sku);
	log.info(json); // {"SKU编码":20190317001,"图片地址":"//img.com/xx.jpg","创建时间":"2019-03-17"}
}

当我们为Sku.class 注册了 TypeAdapter之后,只要是操作Sku.class,那些之前介绍的@SerializedName 、FieldNamingStrategy、Since、Until、Expos通通都黯然失色,失去了效果,只会调用我们实现的SkuTypeAdapter.write(JsonWriter, User) 方法,我想怎么写就怎么写。

Gson API Javadoc: http://www.javadoc.io/doc/com.google.code.gson/gson/2.8.5