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 ([email protected])
 * @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 ([email protected])
 * @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的特性,实际用起来也确实很不舒服,但是当时也没有想到好办法,就暂时那样用了。

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


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

猜你喜欢

转载自blog.csdn.net/yuzhiqiang_1993/article/details/125132064