Gson解析之@SerializedName ,@Expose,Tyypedapter的作用与用法_gson只注册serialize-程序员宅基地

技术标签: 笔记  

转载于:
https://blog.csdn.net/weixin_39789690/article/details/111215333?utm_source=app

https://blog.csdn.net/jdsjlzx/article/details/106409786?utm_source=app
一、序
技术简历的技能树这一项中,JSON 和 GSON 都是常客。但是还有面试候选者将他们的理解停留在最简单的使用上。

“JSON 是一种具有自描述的、独立于语言的、轻量级文本数据交换格式,经常被用于数据的存储和传输。而 GSON 可以帮我们快速的将 JSON 数据,在对象之间序列化和反序列化。”

GSON 的 toJson() 和 fromJson() 这两个方法,是 GSON 最基本的使用方式,它很直观,也没什么好说的。但当被问及 GSON 如何对 JSON 数据容错,如何灵活序列化和反序列化的时候,就有点抓瞎了。

JSON 数据容错,最简单的方式是让前后端数据保持一致,就根本不存在容错的问题,但是现实场景中,并不如我们预期的那般美好。

举两个简单的例子:User 类中的姓名,有些接口返回的 Key 值是 name,而有些返回的是 username,如何做容错呢?再比如 age 字段返回的是如 “18” 这样的字符串,而 Java 对象将其解析成 Int 类型时,虽然 GSON 有一定的类型容错性,这样解析能够成功,但是如果 age 字段的返回值变成了 “” 呢,如何让其不抛出异常,并且设置为默认值 0?

在本文中,我们就来详细看看,GSON 是如何对数据进行容错解析的。

二、GSON 的容错
2.1 GSON 的常规使用
GSON 是 Google 官方出的一个 JSON 解析库,比较常规的使用方式就是用 toJson() 将 Java 对象序列化成 JSON 数据,或者用 fromJson() 将 JSON 数据反序列化成 Java 对象。

// 序列化"cxmydev",
GSON 很方便,大部分时候并不需要我们额外处理什么,拿来即用。唯一需要注意的可能就是泛型擦除,针对泛型的解析,无非就是参数的差异而已。

在数据都很规范的情况下,使用 GSON 就只涉及到这两个方法,但是针对一些特殊的场景,就没那么简单了。

2.2 GSON 的注解
GSON 提供了注解的方式,来配置最简单的灵活性,这里介绍两个注解 @SerializedName 和 @Expose。

@SerializedName 可以用来配置 JSON 字段的名字,最常见的场景来自不同语言的命名方式不统一,有些使用下划线分割,有些使用驼峰命名法。

还是拿 User 类来举例,Java 对象中定义的用户名称,使用的是 userName,而返回的 JSON 数据中,用的是 user_name,这样的差异就可以用 @SerializedName 来解决。

class User{
@SerializedName(“user_name”)
var userName :String? = null
var gender = 0
var age = 0
}
而在前文中,针对同一个 User 对象中的用户名称,现在不同的接口返回有差异,分别为:name、user_name、username,这种差异也可以用 @SerializedName 来解决。

在 @SerializedName 中,还有一个 alternate 字段,可以对同一个字段配置多个解析名称。

class User{
@SerializedName(value = “user_name”,alternate = arrayOf(“name”,“username”))
var userName :String? = null
var gender = 0
var age = 0
}
再来看看 @Expose,它是用来配置一些例外的字段。

在序列化和反序列化的过程中,总有一些字段是和本地业务相关的,并不需要从 JSON 中序列化出来,也不需要在传递 JSON 数据的时候,将其序列化。

这样的情况用 @Expose 就很好解决。从字面上理解,将这个字段暴露出去,就是参与序列化和反序列化。而一旦使用 @Expose,那么常规的 new Gson() 的方式已经不适用了,需要 GsonBuilder 配合 .excludeFieldsWithoutExposeAnnotation() 方法使用。

@Expose 有两个配置项,分别是 serialize 和 deserialize,他们用于指定序列化和反序列化是否包含此字段,默认值均为 True。

class User{
@SerializedName(value = “user_name”,alternate = arrayOf(“name”,“username”))
@Expose
var userName :String? = null
@Expose
var gender = 0
var age = 0

@Expose(serialize = true,deserialize = false)
var genderDesc = ""

}

fun User.gsonTest(){
// 序列化
val user = User()
user.userName = “承香墨影”
user.age = 18
user.gender = 1
user.genderDesc = “男”

val gson = GsonBuilder()
        .excludeFieldsWithoutExposeAnnotation()
        .create()

val jsonStr = gson.toJson(user)
Log.i("cxmydev","json:$jsonStr")
// json:{"gender":1,"genderDesc":"男","user_name":"承香墨影"}

val newUser = gson.fromJson(jsonStr,User::class.java)
Log.i("cxmydev","genderDesc:${newUser.genderDesc}")
// genderDesc:

}
可以看到上面的例子中,genderDesc 用于说明性别的描述,使用 @Expose(serialize = true,deserialize = false) 标记后,这个字段只参与序列化(serialize = true),而不参与反序列化(deserialize = false)。

需要注意的是,一旦开始使用 @Expose 后,所有的字段都需要显式的标记是否“暴露”出来。上例中,age 字段没有 @Expose 注解,所以它在序列化和反序列化的时候,均不会存在。

GSON 中的两个重要的字段注解,就介绍完了,正确的使用他们可以解决 80% 关于 GSON 解析数据的问题,更灵活的使用方式,就不是注解可以解决的了。

2.3 GsonBuilder 灵活解析
就像前面的例子中看到的一样,想要构造一个 Gson 对象,有两种方式,new Gson() 和利用 GsonBuilder 构造。

这两种方式都可以构造一个 Gson 对象,但是在这个 Builder 对象,还提供一些快捷的方法,方便我们更灵活的解析 JSON。

例如默认情况下,GSON 是不会解析为 null 的字段的,而我们可以通过 .serializeNulls() 方法,来让 GSON 序列化为 null 的字段。

// 序列化
val user = User()
user.age = 18
user.gender = 1

val jsonStr = GsonBuilder().create().toJson(user)
Log.i(“cxmydev”,“json:$jsonStr”)
// json:{“age”:18,“gender”:1}

val jsonStr1 = GsonBuilder().serializeNulls().create().toJson(user)
Log.i(“cxmydev”,“json1:$jsonStr1”)
// json1:{“age”:18,“gender”:1,“userName”:null}
GsonBuilder 还提供了更多的操作:

.serializeNulls() :序列化为 null 的字段。

.setDateFormat():设置日期格式,例如:setDateFormat(“yyyy-MM-dd”)。

.disableInnerClassSerialization():禁止序列化内部类。

.generateNonExcutableJson():生成不可直接解析的 JSON,会多 )]}’ 这 4 个字符。

.disableHtmlEscaping():禁止转移 HTML 标签

.setPrettyPrinting():格式化输出

无论是注解还是 GsonBuilder 中提供的一些方法,都是 GSON 针对一些特殊场景下,为我们提供的便捷 API,更复杂一些的场景,就不是它们所能解决的了。

2.4 TypeAdapter
如果前面介绍的规则,都满足不了业务了,没关系,Gson 还有大招,就是使用 TypeAdapter。

这里讲的 TypeAdapter 是一个泛指,它虽然确实是一个 GSON 库中的抽象类,但在 GSON 的使用中,它又不是一个类。

使用 TypeAdapter 就需要用到 GsonBuilder 类中的 registerTypeAdapter(),我们先来看看这个类的方法实现。

public GsonBuilder registerTypeAdapter(Type type, Object typeAdapter) {
G s o n Gson GsonPreconditions.checkArgument(typeAdapter instanceof JsonSerializer>
|| typeAdapter instanceof JsonDeserializer>
|| typeAdapter instanceof InstanceCreator>
|| typeAdapter instanceof TypeAdapter>);
if (typeAdapter instanceof InstanceCreator>) {
instanceCreators.put(type, (InstanceCreator) typeAdapter);
}
if (typeAdapter instanceof JsonSerializer> || typeAdapter instanceof JsonDeserializer>) {
TypeToken> typeToken = TypeToken.get(type);
factories.add(TreeTypeAdapter.newFactoryWithMatchRawType(typeToken, typeAdapter));
}
if (typeAdapter instanceof TypeAdapter>) {
factories.add(TypeAdapters.newFactory(TypeToken.get(type), (TypeAdapter)typeAdapter));
}
return this;
}
可以看到注册方法,需要制定一个数据类型,并且它除了支持 TypeAdapter 之外,还支持 JsonSerializer 和 JsonDeserializer。InstanceCreator 的使用场景太少了,就不谈了。

TypeAdapter(抽象类)、JsonSerializer(接口)、JsonDeserializer(接口) 都可以理解成我们前面说的 TypeAdapter 的泛指,他们具体有什么区别呢?

TypeAdapter 中包含两个主要的方法 write() 和 read() 方法,分别用于接管序列化和反序列化。而有时候,我们并不需要处理这两种情况,例如我们只关心 JSON 是如何反序列化成对象的,那就只需要实现 JsonDeserializer 接口的 deserialize() 方法,反之则实现 JsonSerializer 接口的 serialize() 方法,这让我们的接管更灵活、更可控。

需要注意的是,TypeAdapter 之所以称之为大招,是因为它会导致前面介绍的所有配置都失效。但并不是使用了 TypeAdapter 之后,所有的规则都需要我们自己实现。注意看 registerTypeAdapter() 方法的第一个参数是指定了类型的,它只会针对某个具体的类型进行接管。

举个例子就清楚了,例如前文中提到,当一个 “” 的 JSON 字段,碰上一个 Int 类型的字段时,就会导致解析失败,并抛出异常。

// 序列化
val user = User()
user.age = 18
user.gender = 1

val jsonStr = “{“gender”:”",“user_name”:“承香墨影”}"

val newUser = GsonBuilder().create().fromJson(jsonStr,User::class.java)
Log.i(“cxmydev”,“gender:${gender}”)
在上面的例子中,gender 字段应该是一个 Int 值,而 JSON 字符串中的 gender 为 “”,这样的代码,跑起来会抛 JsonSyntaxException: java.lang.NumberFormatException: empty String 异常。

我们实现 JsonDeserializer 接口,来接管反序列化的操作。

class IntegerDefault0Adapter : JsonDeserializer {
override fun deserialize(json: JsonElement?, typeOfT: Type?, context: JsonDeserializationContext?): Int {
try {
return json!!.getAsInt()
} catch (e: NumberFormatException) {
return 0
}
}
}
当转 Int 出现异常时,返回默认值 0。然后使用 registerTypeAdapter() 方法加入其中。

val newUser = GsonBuilder()
.registerTypeAdapter(Int::class.java, IntegerDefault0Adapter())
.create().fromJson(jsonStr,User::class.java)
Log.i(“cxmydev”,“gender : ${newUser.gender}”)
// gender : 0
TypeAdapter 的使用,到这里就介绍完了,这个大招只要放出来,所有 JSON 解析的问题都不再是问题。TypeAdapter 的适用场景还很多,可以根据具体的需求具体实现,这里就不再过多介绍了。

另外再补充几个 TypeAdapter 的细节。

  1. registerTypeHierarchyAdapter() 的区别

看看源码,细心的朋友应该发现了,注册 TypeAdapter 的时候,还有 registerTypeHierarchyAdapter() 方法,它和 registerTypeAdapter() 方法有什么区别呢?

区别就在于,接管的类型类,是否支持继承。例如前面例子中,我们只接管了 Int 类型,而数字类型还有其他的例如 Long、Float、Double 等并不会命中到。那假如我们注册的是这些数字类型的父类 Number 呢?使用 registerTypeAdapter() 也不会被命中,因为类型不匹配。

此时就可以使用 registerTypeHierarchyAdapter() 方法来注册,它是支持继承的。

  1. TypeAdapterFactory 工厂类的使用

使用 registerXxx() 方法可以链式调用,注册各种 Adapter。

如果嫌麻烦,还可以使用 TypeAdapterFacetory 这个 Adapter 工厂,配合 registerTypeAdapterFactory() 方法,根据类型来返回不同的 Adapter。

其实只是换个了实现方式,并没有什么太大的区别。

  1. @JsonAdapter 注解

@JsonAdapter 和前面介绍的 @SerializedName、@Expose 不同,不是作用在字段上,而是作用在 Java 类上的。

它指定一个“Adapter” 类,可以是 TypeAdapter、JsonSerializer 和 JsonDeserializer 这三个中的一个。

@JsonAdapter 注解只是一个更灵活的配置方式而已,了解一下即可。

三、小结时刻
GSON 很好用,但是也是建立在使用正确的基础上。我见识过一些丑陋的代码,例如多字段场景下,也在 Java 对象中配套写上多个字段,再增加一个方法用于返回多个字段中不会 null 的字段。又或者为了一个 JSON 数据返回的格式,和后端开发“沟通”一下午规范的问题。

坚持规范当然没有错,但是因为别人的问题导致自己的工作无法继续,就不符合精益思维了。

不抽象,就无法深入思考,我们还是就今天的内容做一个简单的小结。

GSON 可以提供了 toJson() 和 fromJson() 两个简便的方法序列化和反序列化 JSON 数据。

通过注解 @SerializedName 可以解决解析字段不一致的问题以及多字段的问题。

通过注解 @Expose 可以解决字段在序列化和反序列化时,字段排除的问题。

GsonBuilder 提供了一些便捷的 API,方便我们解析数据,例如

更灵活的解析,使用 TypeAdapter,可以精准定制序列化和反序列化的全过程。

就总结五条吧,多了也记不住。

----- end —

最简单的利用 @SerializedName 注解来配置多个不同 JSON Key 值,或者再使用 @Expose 来配置一些例外的情况。更复杂一些的数据,可以使用 TypeAdapter 来解决,TypeAdapter 可以说是一颗 GSON 解析 JSON 的银弹,所有复杂数据解析以及容错问题,都可以通过它来解决。

GSON 数据容错实例
就像前文中介绍的一样,GSON 已经提供了一些简单的注解,去做数据的容错处理。更复杂的操作,就需要用到 TypeAdapter 了,需要注意的是,一旦上了 TypeAdapter 之后,注解的配置就会失效。

什么是 TypeAdapter
TypeAdapter 是 GSON 2.1 版本开始支持的一个抽象类,用于接管某些类型的序列化和反序列化。TypeAdapter 最重要的两个方法就是 write() 和 read() ,它们分别接管了序列化和反序列化的具体过程。

如果想单独接管序列化或反序列化的某一个过程,可以使用 JsonSerializer 和 JsonDeserializer 这两个接口,它们组合起来的效果和 TypeAdapter 类似,但是其内部实现是不同的。

简单来说,TypeAdapter 是支持流的,所以它比较省内存,但是使用起来有些不方便。而 JsonSerializer 和 JsonDeserializer 是将数据都读到内存中再进行操作,会比 TypeAdapter 更费内存,但是 API 使用起来更清晰一些。

虽然 TypeAdapter 更省内存,但是通常我们业务接口所使用的那点数据量,所占用的内存其实影响不大,可以忽略不计。

因为 TypeAdapter、JsonSerializer 以及 JsonDeserializer 都需要配合 GsonBuilder.registerTypeAdapter() 方法,所以在本文中,此种接管方式,统称为 TypeAdapter 接管。

空字符串转 0
对于一些强转有效的类型转换,GSON 本身是有一些默认的容错机制的。比如:将字符串 “18” 转换成 Java 中整型的 18,这是被默认支持的。

例如我有一个记录用户信息的 User 类。

class User{
var name = “”
var age = 0
override fun toString(): String {
return “”"
{
“name”:" n a m e " , " a g e " : {name}", "age": name","age":{age}
}
“”".trimIndent()
}
}
User 类中包含 name 和 age 两个字段,其中 age 对应的 JSON 类型,可以是 18 也可以是 “18”,这都是允许的。

{
“name”:“承香墨影”,
“age”:18 // “age”:“18”
}
那假如服务端说,这个用户没有填年龄的信息,所以直接返回了一个空串 “”,那这个时候客户端用 Gson 解析就悲剧了。

这当然是服务端的问题,如果数据明确为 Int 类型,那么就算是默认值也应该是 0 或者 -1。

但遇到这样的情况,你还用默认的 GSON 策略去解析,你将得到一个 Crash。

Caused by: com.google.gson.JsonSyntaxException:

  • java.lang.NumberFormatException:
    – empty String
    那接下来看看如何解决这样的数据容错问题.

因为这里的场景中,只需要反序列化的操作,所以我们实现 JsonDeserializer 接口即可,接管的是 Int 类型。直接上例子吧。

class IntDefaut0Adapter : JsonDeserializer {
override fun deserialize(json: JsonElement?,
typeOfT: Type?,
context: JsonDeserializationContext?): Int {
if (json?.getAsString().equals("")) {
return 0
}
try {
return json!!.getAsInt()
} catch (e: NumberFormatException) {
return 0
}
}
}

fun intDefault0(){
val jsonStr = “”"
{
“name”:“承香墨影”,
“age”:""
}
“”".trimIndent()
val user = GsonBuilder()
.registerTypeAdapter(
Int::class.java,
IntDefaut0Adapter())
.create()
.fromJson(jsonStr,User::class.java)
Log.i(“cxmydev”,“user: ${user.toString()}”)
}
在 IntDefaut0Adapter 中,首先判断数据字符串是否为空字符串 “”,如果是则直接返回 0,否则将其按 Int 类型解析。在这个例子中,将整型 0 作为一个异常参数进行处理。

null、[]、List 转 List
还有一些小伙伴比较关心的,对于 JSONObject 和 JSONArray 兼容的问题。

例如需要返回一个 List,翻译成 JSON 数据就应该是方括号 [] 包裹的 JSONArray。但是在列表为空的时候,服务端返回的数据,什么情况都有可能。

{
“name”:“承香墨影”,
“languages”:[“EN”,“CN”] // 理想的数据
// “languages”:""
// “languages”:null
// “languages”:{}
}
例子的 JSON 中,languages 字段表示当前用户所掌握的语言。当语言字段没有被设置的时候,服务端返回的数据不一致,如何兼容呢?

我们在原本的 User 类中,增加一个 languages 的字段,类型为 ArrayList。

var languages = ArrayList()
在 Java 中,列表集合都会实现 List 接口,所以我们在实现 JsonDeserializer 的时候,解析拦截的应该是 List。

在这个情况下,可以使用 JsonElement 的 isJsonArray() 方法,判断当前是否是一个合法的 JSONArray 的数组,一旦不正确,就直接返回一个空的集合即可。

class ArraySecurityAdapter:JsonDeserializer<List<>>{
override fun deserialize(json: JsonElement, typeOfT: Type?, context: JsonDeserializationContext?): List<
> {

    if(json.isJsonArray()){
        val newGson = Gson()
        return newGson.fromJson(json, typeOfT)
    }else{
        return Collections.EMPTY_LIST
    }
}

}

fun listDefaultEmpty(){
val jsonStr = “”"
{
“name”:“承香墨影”,
“age”:“18”,
“languages”:{}
}
“”".trimIndent()
val user = GsonBuilder()
.registerTypeHierarchyAdapter(
List::class.java,
ArraySecurityAdapter())
.create()
.fromJson(jsonStr,User::class.java)
Log.i(“cxmydev”,“user: ${user.toString()}”)
}
其核心就是 isJsonArray() 方法,判断当前是否是一个 JSONArray,如果是,再具体解析即可。到这一步就很灵活了,你可以直接用 Gson 将数据反序列化成一个 List,也可以将通过一个 for 循环将其中的每一项单独反序列化。

需要注意的是,如果依然想用 Gson 来解析,需要重新创建一个新的 Gson 对象,不可以直接复用 JsonDeserializationContext,否则会造成递归调用。

另外还有一个细节,在这个例子中,调用的是 registerTypeHierarchyAdapter() 方法来注册 TypeAdapter,它和我们前面介绍的 registerTypeAdapter() 有什么区别呢?

通常我们会根据不同的场景,选择不同数据结构实现的集合类,例如 ArrayList 或者 LinkedList。但是 registerTypeAdapter() 方法,要求我们传递一个明确的类型,也就是说它不支持继承,而 registerTypeHierarchyAdapter() 则可以支持继承。

我们想用 List 来替代所有的 List 子类,就需要使用 registerTypeHierarchyAdapter() 方法,或者我们的 Java Bean 中,只使用 List。这两种情况都是可以的。

保留原 Json 字符串
看到这个小标题,可能会有疑问,保留原 Json 字符串是一个什么情况?得到的 Json 数据,本身就是一个字符串,且挺我细细说来。

举个例子,前面定义的 User 类,需要存到 SQLite 数据库中,语言(languages)字段也是需要存储的。说到 SQLite,当然优先使用一些开源的 ORM 框架了,而不少优秀的 ORM-SQLite 框架,都通过外键的形式支持了一对多的存储。例如一篇文章对应多条评论,一条用户信息对应对应多条语言信息。

这种场景下我们当然可以使用 ORM 框架本身提供的一对多的存储形式。但是如果像现在的例子中,只是简单的存储一些有限的数据,例如用户会的语言(languages),这种简单的有限数据,用外键有一些偏重了。

此时我们就想,要是可以直接在 SQLite 中存储 languages 字段的 JSON,将其当成一个字符串去存储,是不是就简单了?把一个多级的结构拉平成一级,剩下的只需要扩展出一个反序列化的方法,对业务来说,这些操作都是透明的。

那拍脑袋想,如果 Gson 有简单的容错,那我们将这个解析的字段类型定义成 String,是不是就可以做到了?

@SerializedName(“languages”)
var languageStr = “”
很遗憾,这并没有办法做到,如果你这样使用,你将得到一个 IllegalStateException 的异常。

Caused by: com.google.gson.JsonSyntaxException: java.lang.IllegalStateException: Expected a string but was BEGIN_ARRAY at line 4 column 18 path $.languages
之所以会出现这样的情况,简单来说,虽然 deserialize() 方法传递的参数都是 JsonElement,但是 JsonElement 只是一个抽象类,最终会根据数据的情况,转换成它的几个实现类的其中之一,这些实现类都是 final class,分别是 JsonObject、JsonArray、JsonPrimitive、JsonNull,这些从命名上就很好理解了,它们代表了不通的 JSON 数据场景,就不过多介绍了。

使用了 Gson 之后,遇到花括号 {} 会生成一个 JsonObject,而字符串则是基本类型的 JsonPrimitive 对象,它们在 Gson 内部的解析流程是不一样的,这就造成了 IllegalStateException 异常。

那么接下来看看如何解决这个问题。

既然 TypeAdapter 是 Gson 解析的银弹,找不到解决方案,用它就对了。思路继续是用 JsonDeserializer 来接管解析,这一次将 User 类的整个解析都接管了。

class UserGsonAdapter:JsonDeserializer{
override fun deserialize(json: JsonElement,
typeOfT: Type?,
context: JsonDeserializationContext?): User {

    var user = User()
    if(json.isJsonObject){
        val jsonObject = JSONObject(json.asJsonObject.toString())
        user.name = jsonObject.optString("name")
        user.age = jsonObject.optInt("age")
        user.languageStr = jsonObject.optString("languages")
        user.languages = ArrayList()
        val languageJsonArray = JSONArray(user.languageStr)
        for(i in 0 until languageJsonArray.length()){
            user.languages.add(languageJsonArray.optString(i))
        }
    }
    return user
}

}

fun userGsonStr(){
val jsonStr = “”"
{
“name”:“承香墨影”,
“age”:“18”,
“languages”:[“CN”,“EN”]
}
“”".trimIndent()
val user = GsonBuilder()
.registerTypeAdapter(
User::class.java,
UserGsonAdapter())
.create()
.fromJson(jsonStr,User::class.java)
Log.i(“cxmydev”,“user: \n${user.toString()}”)
}
在这里我直接使用标准 API org.json 包中的类去解析 JSON 数据,当然你也可以通过 Gson 本身提供的一些方法去解析,这里只是提供一个思路而已。

最终 Log 输出的效果如下:

{
“name”:“承香墨影”,
“age”:18,
“languagesJson”:[“CN”,“EN”],
"languages size:"2
}
在这个例子中,最终解析还是使用了标准的 JSONObject 和 JSONArray 类,和 Gson 没有任何关系,Gson 只是起到了一个桥接的作用,好像这个例子也没什么实际用处。

不谈场景说应用都是耍流氓,那么如果是使用 Retrofit 呢?Retrofit 可以配置 Gson 做为数据的转换器,在其内部就完成了反序列化的过程。这种情况,配合 Gson 的 TypeAdapter,就不需要我们在额外的编写解析的代码了,网络请求走一套逻辑即可。

如果觉得在构造 Retrofit 的时候,为 Gson 添加 TypeAdapter 有些入侵严重了,可以配合 @JsonAdapter 注解使用。

小结
针对服务端返回数据的容错处理,很大一部分其实都是来自双端没有保证数据一致的问题。而针对开发者来说,要做到外部数据均不可信的,客户端不信本地读取的数据、不信服务端返回的数据,服务端也不能相信客户端传递的数据。这就是所谓防御式编程。

言归正传,我们小结一下本文的内容:

TypeAdapter(包含JsonSerializer、JsonDeserializer) 是 Gson 解析的银弹,所有 Json 解析的定制化要求都可以通过它来实现。
registerTypeAdapter() 方法需要制定确定的数据类型,如果想支持继承,需要使用 registerTypeHierarchyAdapter() 方法。
如果数据量不大,推荐使用 JsonSerializer 和 JsonDeserializer。
针对整个 Java Bean 的解析接管,可以使用 @JsonAdapter 注解。

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/nnmmbb/article/details/112059006

智能推荐

ejabberd分析(五)+订阅/添加好友_ejabberd iq-程序员宅基地

文章浏览阅读319次。模块ejabberd_c2s中,状态为session_established2。用户发送iq set 消息到服务器Friends服务器端匹配到[plain] view plaincopyprint?case Name of ...... To = xml:get_attr_s("to", Attrs_ejabberd iq

Python学习笔记02----M*N的棋盘,马从坐下到右上的行走方式_下过象棋的人都知道,马只能走'日'字形(包括旋转90°的日),现在想象一下,给你一个n-程序员宅基地

文章浏览阅读2.4k次。题目:下过象棋的人都知道,马只能走'日'字形(包括旋转90°的日),现在想象一下,给你一个n行m列网格棋盘,棋盘的左下角有一匹马,请你计算至少需要几步可以将它移动到棋盘的右上角,若无法走到,则输出-1.如n=1,m=2,则至少需要1步;若n=1,m=3,则输出-1。#寻找下一步左右可能的点def goNextStep(currentP,n,m): result=[] x=curr..._下过象棋的人都知道,马只能走'日'字形(包括旋转90°的日),现在想象一下,给你一个n

视频集中存储/云存储平台EasyCVR国标GB28181协议接入的报文交互数据包分析_视频磁盘阵列有哪些协议-程序员宅基地

文章浏览阅读1.1k次。设备端才能给服务器传递SIP ID、通道ID以及接入密码等信息。_视频磁盘阵列有哪些协议

部署高可用kubernetes_max-request-bytes-程序员宅基地

文章浏览阅读469次。kubernetes的基本概念写在前面的话整个安装过程中尽量不要出现写死的IP的情况出现,尽量全部使用域名代替IP。环境是ubuntu18.04kubernetes 高可用架构图ETCD高可用API-Server 高可用节点清单制作一个base镜像制作一个base镜像安装和修改通用组件,方便以后的节点部署。修改node的hosts文件如果你使用自己的域名我建议将如下配置配到你的域名管理中。注意:如你使用我这里的域名你需要将此信息写入到机器中的每一台node中(包括master和_max-request-bytes

Java8 Stream:2万字20个实例,玩转集合的筛选、归约、分组、聚合_java8 stream:2万字20个实例-程序员宅基地

文章浏览阅读10w+次,点赞4k次,收藏1.2w次。Java8 Stream横空出世,让我们从繁琐冗长的迭代中解脱出来,集合数据操作变得优雅简洁。这些操作:集合的filter(筛选)、归约(reduce)、映射(map)、收集(collect)、统计(max、min、avg)等等,一行代码即可搞定!让我们一起敲打案例代码,搞定Java8 stream吧!_java8 stream:2万字20个实例

View的事件分发机制(ViewGroup篇)_viewgroup motionevent-程序员宅基地

文章浏览阅读1.1k次。/** * {@inheritDoc} */ @Override public boolean dispatchTouchEvent(MotionEvent ev) { if (mInputEventConsistencyVerifier != null) { mInputEventConsistencyVerifier._viewgroup motionevent

随便推点

python学习[4]: 用python celery + rabbitMQ搭建并行分布式框架及验证_python celery rabbitmq 分布式-程序员宅基地

文章浏览阅读3.3k次。任务解耦(分布式并发处理):假设生产者和消费者分别是两个类。如果让生产者直接调用消费者的某个方法,那么生产者对于消费者就会产生依赖(也就是耦合)。将来如果消费者的代码发生变化,可能会影响到生产者。而如果两者都依赖于某个缓冲区,两者之间不直接依赖,耦合也就相应降低了。生产者直接调用消费者的某个方法,还有另一个弊端:由于函数调用是同步的(或者叫阻塞的),在消费者的方法没有返回之前,生产者只好一直等在那_python celery rabbitmq 分布式

【托福写作】TPO 1_tpo1综合写作-程序员宅基地

文章浏览阅读373次。综合写作[第一次写作]Both in the reading passage and the lecture, there are 3 aspects, which is for the company, for the society, and for the employee himself. And the opinions are totally the opposite.First of all, the four-day week is seen as a costly policy in_tpo1综合写作

[转]The specified module could not be found. (Exception from HRESULT: 0x8007007E)-程序员宅基地

文章浏览阅读2.2k次。问题:I have a managed C++ project (MyLib) that is utilizing 3rd party C++ code and libraries. When I have set a reference to that project (MyLib) I have seen this error. When I put the 3rd party_the specified module could not be found. (exception from hresult: 0x8007007e

linux ln 命令使用参数详解(ln -s 软链接)_软链接ln -s-程序员宅基地

文章浏览阅读1.1w次。source: http://www.jb51.net/LINUXjishu/150570.html作者:佚名 字体:[增加 减小] 来源:互联网 时间:04-04 23:52:55 我要评论这是linux中一个非常重要命令,请大家一定要熟悉。它的功能是为某一个文件在另外一个位置建立一个同不的链接,这个命令最常用的参数是-s,具体用法是:ln -s 源文件 目标文件这是linu_软链接ln -s

[AS3.0]一步一步学ActionScript 3.0(九) -程序员宅基地

文章浏览阅读1.7k次。前两节中,我们讲到了侦听,类与类之前也是可以侦听的,类与类之前的侦听就达到了类和类之前发消息的功能,这其实就是AS3.0中的消息机制。 我们先一个叫做MyClass的类:package net.smilecn{ import flash.display.Sprite; import flash.events.Event; import flash.events

经典的数据库访问接口-程序员宅基地

文章浏览阅读299次。package org.lyq.dao;import java.io.IOException;import java.io.InputStream;import java.sql.CallableStatement;import java.sql.Connection;import java.sql.DriverManager;import java.sql.PreparedStatement..._经典数据库之间接口程序

推荐文章

热门文章

相关标签