moshi 极简封装
moshi 极简封装
blog.csdn.net成就一亿技术人!
目录
- 前言
- Jackson的基本使用
- Jackson获取泛型类型的巧妙处理
- 借鉴jackson优化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的特性,实际用起来也确实很不舒服,但是当时也没有想到好办法,就暂时那样用了。
另外一点的感触就是当你写出来的代码自己都不满意的话,一定要想办法去优化,也一定有办法可以进行优化的,多接触些其他的框架,兴许就找到了解决办法!-
也希望本篇博客能对大佬们有所帮助!
如果你觉得本文对你有帮助,麻烦动动手指顶一下,可以帮助到更多的开发者,如果文中有什么错误的地方,还望指正,转载请注明转自喻志强的博客 ,谢谢!