你真的会用Gson吗?Gson使用指南(4)

原文出处: 怪盗kidou

注:此系列基于Gson 2.4。

本次文章的主要内容:

  • TypeAdapter
  • JsonSerializer与JsonDeserializer
  • TypeAdapterFactory
  • @JsonAdapter注解
  • TypeAdapter与 JsonSerializer、JsonDeserializer对比
  • TypeAdapter实例
  • 结语
  • 后期预告

一、TypeAdapter

TypeAdapter 是Gson自2.0(源码注释上说的是2.1)开始版本提供的一个抽象类,用于接管某种类型的序列化和反序列化过程,包含两个注要方法 write(JsonWriter,T) 和 read(JsonReader) 其它的方法都是final方法并最终调用这两个抽象方法。


1

2

3

4

5

public abstract class TypeAdapter<T> {

    public abstract void write(JsonWriter out, T value) throws IOException;

    public abstract T read(JsonReader in) throws IOException;

    //其它final 方法就不贴出来了,包括`toJson`、`toJsonTree`、`toJson`和`nullSafe`方法。

}

注意:TypeAdapter 以及 JsonSerializer 和 JsonDeserializer 都需要与 GsonBuilder.registerTypeAdapter 示或GsonBuilder.registerTypeHierarchyAdapter配合使用,下面将不再重复说明。

使用示例:


1

2

3

4

5

6

7

User user = new User("怪盗kidou", 24);

user.emailAddress = "[email protected]";

Gson gson = new GsonBuilder()

        //为User注册TypeAdapter

        .registerTypeAdapter(User.class, new UserTypeAdapter())

        .create();

System.out.println(gson.toJson(user));

UserTypeAdapter的定义:


1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

public class UserTypeAdapter extends TypeAdapter<User> {

    @Override

    public void write(JsonWriter out, User value) throws IOException {

        out.beginObject();

        out.name("name").value(value.name);

        out.name("age").value(value.age);

        out.name("email").value(value.email);

        out.endObject();

    }

    @Override

    public User read(JsonReader in) throws IOException {

        User user = new User();

        in.beginObject();

        while (in.hasNext()) {

            switch (in.nextName()) {

                case "name":

                    user.name = in.nextString();

                    break;

                case "age":

                    user.age = in.nextInt();

                    break;

                case "email":

                case "email_address":

                case "emailAddress":

                    user.email = in.nextString();

                    break;

            }

        }

        in.endObject();

        return user;

    }

}

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

再说一个场景,在该系列的第一篇文章就说到了Gson有一定的容错机制,比如将字符串 "24" 转成int 的24,但如果有些情况下给你返了个空字符串怎么办(有人给我评论问到这个问题)?虽然这是服务器端的问题,但这里我们只是做一个示范。

int型会出错是吧,根据我们上面介绍的,我注册一个TypeAdapter 把 序列化和反序列化的过程接管不就行了?


1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

Gson gson = new GsonBuilder()

        .registerTypeAdapter(Integer.class, new TypeAdapter<Integer>() {

            @Override

            public void write(JsonWriter out, Integer value) throws IOException {

                out.value(String.valueOf(value));

            }

            @Override

            public Integer read(JsonReader in) throws IOException {

                try {

                    return Integer.parseInt(in.nextString());

                } catch (NumberFormatException e) {

                    return -1;

                }

            }

        })

        .create();

System.out.println(gson.toJson(100)); // 结果:"100"

System.out.println(gson.fromJson("\"\"",Integer.class)); // 结果:-1

注:测试空串的时候一定是"\"\""而不是""""代表的是没有json串,"\"\""才代表json里的""

你说这一接管就要管两样好麻烦呀,我明明只想管序列化(或反列化)的过程的,另一个过程我并不关心,难道没有其它更简单的方法么? 当然有!就是接下来要介绍的JsonSerializer与JsonDeserializer。

二、JsonSerializer与JsonDeserializer

JsonSerializer 和JsonDeserializer 不用像TypeAdapter一样,必须要实现序列化和反序列化的过程,你可以据需要选择,如只接管序列化的过程就用 JsonSerializer ,只接管反序列化的过程就用 JsonDeserializer ,如上面的需求可以用下面的代码。


1

2

3

4

5

6

7

8

9

10

11

12

13

14

Gson gson = new GsonBuilder()

        .registerTypeAdapter(Integer.class, new JsonDeserializer<Integer>() {

            @Override

            public Integer deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException {

                try {

                    return json.getAsInt();

                } catch (NumberFormatException e) {

                    return -1;

                }

            }

        })

        .create();

System.out.println(gson.toJson(100)); //结果:100

System.out.println(gson.fromJson("\"\"", Integer.class)); //结果-1

下面是所有数字都转成序列化为字符串的例子


1

2

3

4

5

6

7

8

9

10

11

12

13

JsonSerializer<Number> numberJsonSerializer = new JsonSerializer<Number>() {

    @Override

    public JsonElement serialize(Number src, Type typeOfSrc, JsonSerializationContext context) {

        return new JsonPrimitive(String.valueOf(src));

    }

};

Gson gson = new GsonBuilder()

        .registerTypeAdapter(Integer.class, numberJsonSerializer)

        .registerTypeAdapter(Long.class, numberJsonSerializer)

        .registerTypeAdapter(Float.class, numberJsonSerializer)

        .registerTypeAdapter(Double.class, numberJsonSerializer)

        .create();

System.out.println(gson.toJson(100.0f));//结果:"100.0"

注:registerTypeAdapter必须使用包装类型,所以int.class,long.class,float.classdouble.class是行不通的。同时不能使用父类来替上面的子类型,这也是为什么要分别注册而不直接使用Number.class的原因。

上面特别说明了registerTypeAdapter不行,那就是有其它方法可行咯?当然!换成registerTypeHierarchyAdapter 就可以使用Number.class而不用一个一个的当独注册啦!

registerTypeAdapter与registerTypeHierarchyAdapter的区别:

  registerTypeAdapter registerTypeHierarchyAdapter
支持泛型
支持继承

注:如果一个被序列化的对象本身就带有泛型,且注册了相应的TypeAdapter,那么必须调用Gson.toJson(Object,Type),明确告诉Gson对象的类型。


1

2

3

4

5

6

7

8

9

10

11

12

Type type = new TypeToken<List<User>>() {}.getType();

TypeAdapter typeAdapter = new TypeAdapter<List<User>>() {

   //略

};

Gson gson = new GsonBuilder()

        .registerTypeAdapter(type, typeAdapter)

        .create();

List<User> list = new ArrayList<>();

list.add(new User("a",11));

list.add(new User("b",22));

//注意,多了个type参数

String result = gson.toJson(list, type);

三、TypeAdapterFactory

TypeAdapterFactory,见名知意,用于创建TypeAdapter的工厂类,通过对比Type,确定有没有对应的TypeAdapter,没有就返回null,与GsonBuilder.registerTypeAdapterFactory配合使用。


1

2

3

4

5

6

7

8

Gson gson = new GsonBuilder()

    .registerTypeAdapterFactory(new TypeAdapterFactory() {

        @Override

        public <T> TypeAdapter<T> create(Gson gson, TypeToken<T> type) {

            return null;

        }

    })

    .create();

四、@JsonAdapter注解

JsonAdapter相较之前介绍的SerializedNameFieldNamingStrategySinceUntilExpos这几个注解都是比较特殊的,其它的几个都是用在POJO的字段上,而这一个是用在POJO类上的,接收一个参数,且必须是TypeAdpaterJsonSerializerJsonDeserializer这三个其中之一。

上面说JsonSerializerJsonDeserializer都要配合GsonBuilder.registerTypeAdapter使用,但每次使用都要注册也太麻烦了,JsonAdapter就是为了解决这个痛点的。

使用方法(以User为例):


1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

@JsonAdapter(UserTypeAdapter.class) //加在类上

public class User {

    public User() {

    }

    public User(String name, int age) {

        this.name = name;

        this.age = age;

    }

    public User(String name, int age, String email) {

        this.name = name;

        this.age = age;

        this.email = email;

    }

    public String name;

    public int age;

    @SerializedName(value = "emailAddress")

    public String email;

}

使用时不用再使用 GsonBuilder去注册UserTypeAdapter了。
注:@JsonAdapter 仅支持 TypeAdapterTypeAdapterFactory


1

2

3

4

5

Gson gson = new Gson();

User user = new User("怪盗kidou", 24, "[email protected]");

System.out.println(gson.toJson(user));

//结果:{"name":"怪盗kidou","age":24,"email":"[email protected]"}

//为区别结果,特意把email字段与@SerializedName注解中设置的不一样

注意:JsonAdapter的优先级比GsonBuilder.registerTypeAdapter的优先级更高。

五、TypeAdapter与 JsonSerializer、JsonDeserializer对比

  TypeAdapter JsonSerializer、JsonDeserializer
引入版本 2.0 1.x
Stream API 支持 不支持*,需要提前生成JsonElement
内存占用 TypeAdapter
效率 TypeAdapter
作用范围 序列化 和 反序列化 序列化 或 反序列化

六、TypeAdapter实例

注:这里的TypeAdapter泛指TypeAdapterJsonSerializerJsonDeserializer
这里的TypeAdapter 上面讲了一个自动将 字符串形式的数值转换成int型时可能出现 空字符串的问题,下面介绍一个其它读者的需求:

服务器返回的数据中data字段类型不固定,比如请求成功data是一个List,不成功的时候是String类型,这样前端在使用泛型解析的时候,怎么去处理呢?

其实这个问题的原因主要由服务器端造成的,接口设计时没有没有保证数据的一致性,正确的数据返回姿势:同一个接口任何情况下不得改变返回类型,要么就不要返,要么就返空值,如null[],{}

但这里还是给出解决方案:
方案一:


1

2

3

4

5

6

7

8

9

10

11

12

13

Gson gson = new GsonBuilder().registerTypeHierarchyAdapter(List.class, new JsonDeserializer<List<?>>() {

    @Override

    public List<?> deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException {

        if (json.isJsonArray()){

            //这里要自己负责解析了

            Gson newGson = new Gson();

            return newGson.fromJson(json,typeOfT);

        }else {

            //和接口类型不符,返回空List

            return Collections.EMPTY_LIST;

        }

    }

}).create();

方案二:


1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

Gson gson = new GsonBuilder().registerTypeHierarchyAdapter(List.class, new JsonDeserializer<List<?>>() {

    @Override

    public List<?> deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException {

        if (json.isJsonArray()) {

            JsonArray array = json.getAsJsonArray();

            Type itemType = ((ParameterizedType) typeOfT).getActualTypeArguments()[0];

            List list = new ArrayList<>();

            for (int i = 0; i < array.size(); i++) {

                JsonElement element = array.get(i);

                Object item = context.deserialize(element, itemType);

                list.add(item);

            }

            return list;

        } else {

            //和接口类型不符,返回空List

            return Collections.EMPTY_LIST;

        }

    }

}).create();

要注意的点:

  • 必须使用registerTypeHierarchyAdapter方法,不然对List的子类无效,但如果POJO中都是使用List,那么可以使用registerTypeAdapter
  • 对于是数组的情况,需要创建一个新的Gson,不可以直接使用context,不然gson又会调我们自定义的JsonDeserializer造成递归调用,方案二没有重新创建Gson,那么就需要提取出List<E>中E的类型,然后分别反序列化适合为E手动注册了TypeAdaper的情况。
  • 从效率上推荐方案二,免去重新实例化Gson和注册其它TypeAdapter的过程。

结语

Gson系列总算是完成了,感觉写得越来越差了,我怕我写得太啰嗦,也不能总是大片大片的贴代码,所以可能有的地方写得并不详细,排版也不美观,但都些都不重点,重点是Gson里我们能用上的都一一介绍一遍,只要你确确实实把我这几篇文章上的内容都学会的话,以后Gson上的任何问题都不再是问题,当然可能很多内容对于实际的开发中用的并不多,但下次有什么疑难杂症就难不倒你了。

本系列不提供Demo源码,最重要的是自己实验。

写一篇文章还是要花不少时间和精力,要写示例、调式、组织语言、码字等等,加上关注的人在慢慢的增加的同时既给了我动力也给我不少压力,如有纰漏或者更好的例子都可以和我交流。

后期预告:

之前有人给我评论说 出一点 retrofit 相关内容,我想了想,出是会出,但在此之前我想先出大概3~4篇文章用于介绍 泛型、反射、注解和HTTP 的相关内容,当你确实掌握之后,我打包票你只需要看一遍Retrofit官方教程的代码示例,都不用看其它英文说明,就可以轻松玩转Retrofit。不服来战!

本系列:

时间: 2024-10-24 05:31:16

你真的会用Gson吗?Gson使用指南(4)的相关文章

你真的会用Gson吗?Gson使用指南(3)

原文出处: 怪盗kidou 注:此系列基于Gson 2.4. 本次的主要内容: 字段过滤的几种方法 基于@Expose注解 基于版本 基于访问修饰符 基于策略(作者最常用) POJO与JSON的字段映射规则 一.字段过滤的几种方法 字段过滤Gson中比较常用的技巧,特别是在Android中,在处理业务逻辑时可能需要在设置的POJO中加入一些字段,但显然在序列化的过程中是不需要的,并且如果序列化还可能带来一个问题就是 循环引用 ,那么在用Gson序列化之前为不防止这样的事件情发生,你不得不作另外的

你真的会用Gson吗?Gson使用指南(2)

注:此系列基于Gson 2.4. 上一篇文章 你真的会用Gson吗?Gson使用指南(1) 我们了解了Gson的基础用法,这次我们继续深入了解Gson的使用方法. 本次的主要内容: Gson的流式反序列化 Gson的流式序列化 使用GsonBuilder导出null值.格式化输出.日期时间及其它小功能 一.Gson的流式反序列化 自动方式 Gson提供了fromJson()和toJson() 两个直接用于解析和生成的方法,前者实现反序列化,后者实现了序列化.同时每个方法都提供了重载方法,我常用的

你真的会用Gson吗?Gson使用指南(一)

你真的会用Gson吗?Gson使用指南(一)JSON (官网) 是一种文本形式的数据交换格式,它比XML更轻量.比二进制容易阅读和编写,调式也更加方便.其重要性不言而喻.解析和生成的方式很多,Java中最常用的类库有:JSON-Java.Gson.Jackson.FastJson等,本次我向大家介绍的是Gson. 本篇文章的主要内容: Gson的基本用法属性重命名 @SerializedName 注解的使用Gson中使用泛型一.Gson的基本用法Gson提供了fromJson() 和toJson

你真的会用Gson吗?Gson使用指南(1)

JSON (官网) 是一种文本形式的数据交换格式,它比XML更轻量.比二进制容易阅读和编写,调式也更加方便.其重要性不言而喻.解析和生成的方式很多,Java中最常用的类库有:JSON-Java.Gson.Jackson.FastJson等. 注:此系列基于Gson 2.4. 对Gson使用很自信的大大可以点击关闭啦. 本篇文章的主要内容: Gson的基本用法 属性重命名 @SerializedName 注解的使用 Gson中使用泛型 一.Gson的基本用法 Gson提供了fromJson() 和

【Gson】Gson 使用笔记

Gson 是一款非常好用的 json 处理工具,只需要导一个 gson 的 jar 包就可以使用了,这里简单记录 Gson 的两种常用方法 Json 转 对象 Object object = new Gson().fromJson(json, Object.class); Json 转 Map private Map<String, String> parseJson(String json){ GsonBuilder gsonBuilder = new GsonBuilder(); Gson

一起来开发Android的天气软件(四)——使用Gson解析数据

离上一篇文章过去才4.5天,我们赶紧趁热打铁继续完毕该系列的天气软件的开发. 承接上一章的内容使用Volley实现网络的通信.返回给我们的是这一串Json数据{"weatherinfo":{"city":"杭州","cityid":"101210101","temp1":"1℃","temp2":"10℃","weat

[转]用GSON 五招之内搞定任何JSON数组

关于GSON的入门级使用,这里就不提了,如有需要可以看这篇博文 <Google Gson的使用方法,实现Json结构的相互转换> ,写的很好,通俗易懂. 我为什么写这篇文章呢?因为前几晚跟好友 xiasuhuei321 探讨了一下GSON解析复杂的JSON的时候,能不能只解析源数据中的数组,甚至只解析数组的某一部分.探讨了二十分钟,得出结论:没用过,不知道. 所以今天特地研究了一下,发现真的So Easy!之前想复杂了,学习的过程中,发现有五种方式分别搞定不同情况的JSON数组,也就是今天说的

Gson全解析(上)-Gson基础

前言 最近在研究Retrofit中使用的Gson的时候,发现对Gson的一些深层次的概念和使用比较模糊,所以这里做一个知识点的归纳整理. Gson(又称Google Gson)是Google公司发布的一个开放源代码的Java库,主要用途为序列化Java对象为JSON字符串,或反序列化JSON字符串成Java对象.而JSON(JavaScript Object Notation) 是一种轻量级的数据交换格式,易于人阅读和编写,同时也易于机器解析和生成,广泛应用于各种数据的交互中,尤其是服务器与客户

Gson全解析(中)-TypeAdapter的使用

TypeAdapter介绍 前面的Gson全解析(上)中我们理解并分别运用了JsonSerializer和JsonDeserializer进行JSON和java实体类之间的相互转化.这里利用TypeAdapter来更加高效的完成这个需求. 之前在上一篇文中提到的JsonSerializer和JsonDeserializer解析的时候都利用到了一个中间件-JsonElement,比如下方的序列化过程.可以看到我们在把Java对象转化为JSON字符串的时候都会用到这个中间件JsonElement J