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 toJson(adapter: JsonAdapter, 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 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 toJson(src: T, parameterizedType: ParameterizedType, indent: String = ""): String {
val adapter = moshi.adapter(parameterizedType)
return this.toJson(adapter = adapter, src = src, indent = indent)
}

inline fun fromJson(adapter: JsonAdapter, jsonStr: String): T? {
try {
return adapter.fromJson(jsonStr)
} catch (e: Exception) {
e.printStackTrace()
}
return null
}

/**
* json 反序列化为 T
* @param jsonStr String
* @return T?
*/
inline fun fromJson(jsonStr: String): T? {
val adapter = moshi.adapter(T::class.java)
return this.fromJson(adapter, jsonStr)
}

/**
* json 反序列化为 MutableList
* @param jsonStr String
* @return MutableList?
*/
inline fun fromJsonToList(jsonStr: String): MutableList? {
val parameterizedType = Types.newParameterizedType(MutableList::class.java, T::class.java)
return fromJson<MutableList>(jsonStr, parameterizedType)
}

/**
* json 反序列化为 T, 指定 parameterizedType,复杂数据用
* @param jsonStr String
* @param parameterizedType ParameterizedType
* @return T?
*/
inline fun fromJson(jsonStr: String, parameterizedType: ParameterizedType): T? {
val adapter = moshi.adapter(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()
(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>>(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 // 自定义的类,用来包装泛型

然后就是提供个方法用来通过子类获取具体化的泛型类型

inline fun getGenericType(): Type {
val type =
object :
MoshiTypeReference() {}::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 // 自定义的类,用来包装泛型

val moshi = Moshi.Builder()
.addLast(KotlinJsonAdapterFactory()).build()

inline fun toJson(src: T, indent: String = ""): String {

try {

val jsonAdapter = moshi.adapter(getGenericType())
return jsonAdapter.indent(indent).toJson(src)
} catch (e: Exception) {
e.printStackTrace()
}
return ""

}

inline fun fromJson(jsonStr: String): T? {
try {
val jsonAdapter = moshi.adapter(getGenericType())
return jsonAdapter.fromJson(jsonStr)
} catch (e: Exception) {
e.printStackTrace()
}
return null
}

inline fun getGenericType(): Type {
val type =
object :
MoshiTypeReference() {}::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(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>>(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>(userListJsonStr)
println("userList = ${userList}")

println(userList!!.get(0).hobby.get(0).name)

运行结果:-
在这里插入图片描述

可以看到,运行正常。-
相较于文章开头的那种封装,改造后的用起来就舒服多了,就toJson和fromJson就完事儿了。

其实没搞明白为啥moshi不像jackson一样直接封装一下,给我们提供一个用起来简单一些的api,而让开发者使用Types.newParameterizedType去指定类型,在处理嵌套类型时写起来确实是比较麻烦。


总结

好了,本篇博客实际上就是对之前moshi实战那篇博客的收尾了,其实之前封装后就感觉不太对劲,完全没有发挥出kotlin的特性,实际用起来也确实很不舒服,但是当时也没有想到好办法,就暂时那样用了。

另外一点的感触就是当你写出来的代码自己都不满意的话,一定要想办法去优化,也一定有办法可以进行优化的,多接触些其他的框架,兴许就找到了解决办法!-
也希望本篇博客能对大佬们有所帮助!


如果你觉得本文对你有帮助,麻烦动动手指顶一下,可以帮助到更多的开发者,如果文中有什么错误的地方,还望指正,转载请注明转自喻志强的博客 ,谢谢!

跳转到 Cubox 查看

posted @ 2023-04-21 10:55  cps666  阅读(363)  评论(0)    收藏  举报