moshi 极简封装
Posted XeonYu
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了moshi 极简封装相关的知识,希望对你有一定的参考价值。
目录
前言
之前写了一篇文章是介绍moshi的基本使用和实战,感兴趣的可以先看一下对kotlin友好的现代 JSON 库 moshi 基本使用和实战
在那篇文章中,我们最后针对moshi做了个封装,代码大致如下:
import com.squareup.moshi.JsonAdapter
import com.squareup.moshi.Moshi
import com.squareup.moshi.Types
import com.squareup.moshi.kotlin.reflect.KotlinJsonAdapterFactory
import java.lang.reflect.ParameterizedType
/**
* @description: 基于moshi的json转换封装
* @author : yuzhiqiang (zhiqiang.yu.xeon@gmail.com)
* @date : 2022/3/13
* @time : 6:29 下午
*/
object MoshiUtil
val moshi = Moshi.Builder().addLast(KotlinJsonAdapterFactory()).build()
fun <T> toJson(adapter: JsonAdapter<T>, src: T, indent: String = ""): String
try
return adapter.indent(indent).toJson(src)
catch (e: Exception)
e.printStackTrace()
return ""
/**
* T 类型对象序列化为 json
* @param src T
* @param indent String
* @return String
*/
inline fun <reified T> toJson(src: T, indent: String = ""): String
val adapter = moshi.adapter(T::class.java)
return this.toJson(adapter = adapter, src = src, indent = indent)
/**
* 将 T 序列化为 json,指定 parameterizedType,适合复杂类型
* @param src T
* @param parameterizedType ParameterizedType
* @param indent String
* @return String
*/
inline fun <reified T> toJson(src: T, parameterizedType: ParameterizedType, indent: String = ""): String
val adapter = moshi.adapter<T>(parameterizedType)
return this.toJson(adapter = adapter, src = src, indent = indent)
inline fun <reified T> fromJson(adapter: JsonAdapter<T>, jsonStr: String): T?
try
return adapter.fromJson(jsonStr)
catch (e: Exception)
e.printStackTrace()
return null
/**
* json 反序列化为 T
* @param jsonStr String
* @return T?
*/
inline fun <reified T> fromJson(jsonStr: String): T?
val adapter = moshi.adapter(T::class.java)
return this.fromJson(adapter, jsonStr)
/**
* json 反序列化为 MutableList<T>
* @param jsonStr String
* @return MutableList<T>?
*/
inline fun <reified T> fromJsonToList(jsonStr: String): MutableList<T>?
val parameterizedType = Types.newParameterizedType(MutableList::class.java, T::class.java)
return fromJson<MutableList<T>>(jsonStr, parameterizedType)
/**
* json 反序列化为 T, 指定 parameterizedType,复杂数据用
* @param jsonStr String
* @param parameterizedType ParameterizedType
* @return T?
*/
inline fun <reified T> fromJson(jsonStr: String, parameterizedType: ParameterizedType): T?
val adapter = moshi.adapter<T>(parameterizedType)
return this.fromJson(adapter = adapter, jsonStr = jsonStr)
虽然能满足我们日常的使用需求,但是显然使用起来是不够简洁的,
原因在于如果传进来的泛型如果是一个嵌套的相对复杂的泛型时,没有找到一个比较好的方法去获取泛型的实际type,因此当时是选择了一个将对象和LIst分开的形式做了个封装,并且单独针对BaseResp这种固定结构又加了一个封装。虽然能达到效果,但是需要使用人针对不同的场景选择不同的方法,在实际开发中是有一些记忆成本在的。
在之前的文章中,也提到了Jackson对kotlin提供了单独的kotlin-module处理,在使用jackson时我发现jackson的api就非常简单,那jackson是怎么处理的呢,我们是不是可以借鉴一下呢?
Jackson的基本使用
首先,先来看一下jackson的使用。
jackson的基本使用也是非常简单的
添加依赖
implementation("com.fasterxml.jackson.module:jackson-module-kotlin:2.13.3")
序列化&反序列化
val userList = mutableListOf<User>()
(1..10).forEach
val user = User("name$it", it, arrayListOf(Hobby("类型", "爱好$it")))
userList.add(user)
val baseResp = BaseResp(200, "ok", userList)
/*序列化*/
val baseRespJsonStr = jacksonObjectMapper().writeValueAsString(baseResp)
println("baseRespJsonStr = $baseRespJsonStr")
/*反序列化*/
val baseRespList = jacksonObjectMapper().readValue<BaseResp<List<User>>>(baseRespJsonStr)
println("baseRespList = $baseRespList")
是的,你没看错,就是这么简单,先来看下运行结果
也就是说,jackson-kotlin-module已经给我们提供好了跟简洁的api了,我们在使用时几乎不需要做什么封装直接用就行。
Jackson获取泛型类型的巧妙处理
那jackson是怎么做到的呢,我们来看一波源码,主要看反序列化方法的源码就行
可以看到,readValue是一个ObjectMapper的扩展方法,最终调用了readValue方法。
我们要注意的关键点就是jackson是怎么拿到的泛型的类型信息的。
关键点在这个jacksonTypeRef()
, 我们来看一下这个到底是个什么玩意。
点击去后发现他是一个 TypeReference<T>
类型的匿名对象
那我们再去看一下 TypeReference<T>
发现TypeReference实际就是一个泛型抽象类, 提供的构造方法中获取到了参数化类型。
其原理就是通过子类把泛型类型具体化,其实仔细想一想之前我们常用的Gson中的TypeToken,也是类似的做法
借鉴jackson优化moshi的封装
那知道了原理之后就好办了,我们在抄一波改造下之前的封装就好了。
首先我们也准备一个抽象类用来包装泛型
abstract class MoshiTypeReference<T> // 自定义的类,用来包装泛型
然后就是提供个方法用来通过子类获取具体化的泛型类型
inline fun <reified T> getGenericType(): Type
val type =
object :
MoshiTypeReference<T>() ::class.java
.genericSuperclass
.let it as ParameterizedType
.actualTypeArguments
.first()
return type
拿到类型就好说了,把之前哪些冗余的封装方法都删掉,我们只需要保留两个方法即可,toJson和fromJson
完整代码如下,拷贝直接就能用
import com.squareup.moshi.Moshi
import com.squareup.moshi.adapters.PolymorphicJsonAdapterFactory
import com.squareup.moshi.kotlin.reflect.KotlinJsonAdapterFactory
import com.xeon.json.kotlin.data.polym.moshi.Employee
import com.xeon.json.kotlin.data.polym.moshi.Person
import com.xeon.json.kotlin.data.polym.moshi.Student
import java.lang.reflect.ParameterizedType
import java.lang.reflect.Type
/**
* @description: 基于moshi的json转换封装
* @author : yuzhiqiang (zhiqiang.yu.xeon@gmail.com)
* @date : 2022/3/13
* @time : 6:29 下午
*/
object MoshiUtil
abstract class MoshiTypeReference<T> // 自定义的类,用来包装泛型
val moshi = Moshi.Builder()
.addLast(KotlinJsonAdapterFactory()).build()
inline fun <reified T> toJson(src: T, indent: String = ""): String
try
val jsonAdapter = moshi.adapter<T>(getGenericType<T>())
return jsonAdapter.indent(indent).toJson(src)
catch (e: Exception)
e.printStackTrace()
return ""
inline fun <reified T> fromJson(jsonStr: String): T?
try
val jsonAdapter = moshi.adapter<T>(getGenericType<T>())
return jsonAdapter.fromJson(jsonStr)
catch (e: Exception)
e.printStackTrace()
return null
inline fun <reified T> getGenericType(): Type
val type =
object :
MoshiTypeReference<T>() ::class.java
.genericSuperclass
.let it as ParameterizedType
.actualTypeArguments
.first()
return type
好了,这样看起来是不是就简洁很多了呢,就简单的toJson和fromJson两个方法即可。
使用
改造完成之后我们来使用一波
/*对象反序列化泛型测试*/
val user = User("喻志强", 19)
val userStr = MoshiUtil.toJson(user)
val userObj = MoshiUtil.fromJson<User>(userStr)
println("userObj = $userObj!!.name")
/*复杂对象反序列化时的泛型测试*/
val baseRespJsonStr = """
"code":200,"msg":"ok","data":["name":"name1","age":1,"hobby":["type":"类型","name":"爱好1"],"name":"name2","age":2,"hobby":["type":"类型","name":"爱好2"],"name":"name3","age":3,"hobby":["type":"类型","name":"爱好3"],"name":"name4","age":4,"hobby":["type":"类型","name":"爱好4"],"name":"name5","age":5,"hobby":["type":"类型","name":"爱好5"],"name":"name6","age":6,"hobby":["type":"类型","name":"爱好6"],"name":"name7","age":7,"hobby":["type":"类型","name":"爱好7"],"name":"name8","age":8,"hobby":["type":"类型","name":"爱好8"],"name":"name9","age":9,"hobby":["type":"类型","name":"爱好9"],"name":"name10","age":10,"hobby":["type":"类型","name":"爱好10"]]
""".trimIndent()
val baseResp = MoshiUtil.fromJson<BaseResp<List<User>>>(baseRespJsonStr)
println("baseResp = $baseResp")
if (baseResp == null)
println("解析异常")
println(baseResp!!.data.get(0).name)
val baseRespToJson = MoshiUtil.toJson(baseResp)
println("baseRespToJson = $baseRespToJson")
/*直接是一个list测试*/
val userListJsonStr = MoshiUtil.toJson(baseResp.data)
println("userListJsonStr = $userListJsonStr")
val userList = MoshiUtil.fromJson<List<User>>(userListJsonStr)
println("userList = $userList")
println(userList!!.get(0).hobby.get(0).name)
运行结果:
可以看到,运行正常。
相较于文章开头的那种封装,改造后的用起来就舒服多了,就toJson和fromJson就完事儿了。
其实没搞明白为啥moshi不像jackson一样直接封装一下,给我们提供一个用起来简单一些的api,而让开发者使用Types.newParameterizedType
去指定类型,在处理嵌套类型时写起来确实是比较麻烦。
总结
好了,本篇博客实际上就是对之前moshi实战那篇博客的收尾了,其实之前封装后就感觉不太对劲,完全没有发挥出kotlin的特性,实际用起来也确实很不舒服,但是当时也没有想到好办法,就暂时那样用了。
另外一点的感触就是当你写出来的代码自己都不满意的话,一定要想办法去优化,也一定有办法可以进行优化的,多接触些其他的框架,兴许就找到了解决办法!
也希望本篇博客能对大佬们有所帮助!
如果你觉得本文对你有帮助,麻烦动动手指顶一下,可以帮助到更多的开发者,如果文中有什么错误的地方,还望指正,转载请注明转自喻志强的博客 ,谢谢!
以上是关于moshi 极简封装的主要内容,如果未能解决你的问题,请参考以下文章
JavaScript学习总结(十三)——极简主义法编写JavaScript类
Flutter 重构:基于 PopupRoute 的极简弹窗