Retrofit 返回值序列化增加 JSONObject 类型

982 阅读3分钟

Retrofit 返回值序列化增加 JSONObject 类型支持

{"msg":"success","code":0,"data":{}} 对应类为

data class Result(val code: Int = 0, val msg: String = "", val data: JSONObject = JSONObject())

让结果 data 不再为空空花括号 { }

一、Retrofit 返回值序列化,变化部分设置为 JSONObject 解决方案

部分属性使用 JSONObject 类型声明后,接收到内容值一直为空,解决方案

  1. 自定义解析类型(反序列化) 继承 JsonDeserializer<T> 实现自己的解析,仅针对 T 这类型生效,不影响其他类
  2. 将该类型增加至 Gson
    // XX 为你最终要的类,XXType 为第一点自定义的类
    val gson = GsonBuilder()
            .registerTypeAdapter(XX::class.java, XXType())
            .create()
    
  3. 将该类添加到 GsonConverterFactory GsonConverterFactory.create(gson)
    val retrofit = Retrofit.Builder()
                .baseUrl("http://localhost:8080/")
                .addCallAdapterFactory(RxJava2CallAdapterFactory.create())
                .addConverterFactory(GsonConverterFactory.create(gson))
                .build()
    

详细的例子可以直接拉到文章下面查阅。

二、本文需求来源

在网络请求中,通常会以固定格式返回数据,方便客户端做解析,如 {"msg":"success","code":0,"data":{}},为了解析通用,一般会使用泛型来做数据解析,如下


data class Result<T>(val code:Int,val msg:String,val data:T)

data class UserInfo(val name:String)

声明接口

interface Api{

	@Get("/login")
	fun login():Observable<Result<UserInfo>>

	//这种是不支持,反序列化必须知道具体类型,泛型会被擦除,所以无法得知具体类型抛出异常
	//fun <T> login():Observable<Result<T>>
}

具体使用

fun test{
	
	//省略
	val api = retrofit.create(Api::class.java)
	api.login().subscribe({result -> println("success")},
		{e -> printStackTrace()})

}

如果存在接口少,定义出各类具体的 data 实例,也不是什么大问题,无非是不同接口新建一个新类用来反序列化构建而已,但如果遇到多重嵌套,那编写的类就多了,如下情况

{"msg":"success","code":0,"data":{"pic":[{"id":1,"name":"001.jpg","url":"file/001.jpg"},{"id":2,"name":"003.jpg","url":"file/003.jpg"}]}}

这时还想使用上面的 Result<T>,那么需要新建一个子项 Item,以及 Pic 类包含 List,所以对应编写如下

data class Item(val name:String,val url:String)

data class Pic(val pic:List<Item>)

interface Api{

	@Get("/getData")
	fun getData():Observable<Result<Data>>
}

fun test(){
    api.test()
        .subscribe({ result ->
            println(result.toString())
        }, { e ->
            e.printStackTrace()
        })
}

输出
Result(code=0, msg=success, data=Pic(pic=[Item(name=001.jpg, url=file/001.jpg), Item(name=003.jpg, url=file/003.jpg)]))

kotlin 写还好,如果是 java 类数量就暴增。

那么能否使用 JSONObject 来表示 data 这个参数?简单尝试一下:

data class Result(val code:Int,val msg:String,val data:JSONObject)

interface Api{

	@Get("/login")
	fun login():Observable<Result>

}

fun login(){

	api.login()
        .subscribe({ result ->
            println(result.toString())
        }, { e ->
            e.printStackTrace()
        })
}

//输出的结果  Result(code=0, msg=success, data={})

//这个结果和你直接定义返回类型是 JSONObject 一致
//无法匹配到类型进行反序列化,得到结果一直是 {}
interface Api{

	@Get("/login")
	fun login():Observable<JSONObjcet>

}

以上,会有一种解决方案,直接舍弃 ResultJSONObject 来替代,替换 GsonConverterFactory.create() 为能解析 JSONObject 的 Converter 即可,本文不做描述。

三、具体解决示例

替换 GsonConverterFactory.create() ,这不是本文的目标,我的目的是使用通用类,让 data 类型为 JSONObject,即保留部分属性,让另一个属性(对象)以JSONObject 形式存在,测试结果很明显,Gson 不支持 JSONObject 这个类型,那么我们直接定义一个 Result 类型解析即可,如下

//通用类
data class Result(val code: Int = 0, val msg: String = "", val data: JSONObject = JSONObject())

//解析类
class JSONResultType : JsonDeserializer<Result> {
    override fun deserialize(json: JsonElement, typeOfT: Type, context: JsonDeserializationContext): Result {
        val result = if (json.isJsonObject) {
            val obj = json.asJsonObject
            val code = obj.get("code").asInt
            val msg = obj.get("msg").asString
            val data = JSONObject(obj.getAsJsonObject("data").toString())
            Result(code, msg, data)
        } else {
            Result()
        }
        return result
    }
}

使用,在 Retrofit 初始化增加类型即可,如下

fun test(){

	val gson = GsonBuilder()
                .registerTypeAdapter(Result::class.java, JSONResultType())
                .create()

    val retrofit = Retrofit.Builder()
        .baseUrl("http://localhost:8080/")
        .addCallAdapterFactory(RxJava2CallAdapterFactory.create())
        .addConverterFactory(GsonConverterFactory.create(gson))
        .build()
}

测试调用,输出结果如下:

========== Result<UserInfo> ============== Result(code=0, msg=success, data=UserInfo(name=zhangsan)) ========== JSONResult ============== JSONResult(code=0, msg=success, data={"name":"zhangsan"})

至于为什么不直接将整个结果替换为 JSONObject,主要是懒,kotlin 一个 . 解决的事,才不想用 optXXX(),以上。

本文结束,感谢阅读