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实际就是一个泛型抽象类, 提供的构造方法中获取到了参数化类型。
这个就有点巧妙了,也就是说通过创建子类对象时就能拿到传入泛型的完整类型信息了。

借鉴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

有了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 极简封装的主要内容,如果未能解决你的问题,请参考以下文章

moshi 极简封装

moshi 极简封装

JavaScript学习总结(十三)——极简主义法编写JavaScript类

Flutter 重构:基于 PopupRoute 的极简弹窗

对kotlin友好的现代 JSON 库 moshi 基本使用和实战

对kotlin友好的现代 JSON 库 moshi 基本使用和实战