Moshi 的 Kotlin 代码生成器有啥用?

Posted

技术标签:

【中文标题】Moshi 的 Kotlin 代码生成器有啥用?【英文标题】:What's the use of Moshi's Kotlin codegen?Moshi 的 Kotlin 代码生成器有什么用? 【发布时间】:2020-02-18 11:09:50 【问题描述】:

我最近开始在我的 android 应用中使用 Moshi,我很想知道更多关于 @JsonClass(generateAdapter = true) 注释 真正 的作用。

示例数据类:

data class Person(
    val name: String
)

我可以对这个类进行序列化/反序列化,如下所示:

val moshi: Moshi = Moshi.Builder().build()
moshi.adapter(Person::class.java).toJson(Person())

在此处使用 @JsonClass 注释,因此 codegen 不会启动。

我的问题是,为什么以及何时需要使用@JsonClass(generateAdapter = true)

【问题讨论】:

【参考方案1】:

这是三个问题

    为什么代码生成很有用

代码生成可用作反射式 moshi-kotlin 的编译时替代方案。它们都很有用,因为它们本身就理解 Kotlin 代码及其语言特性。没有它们,Moshi 将无法理解 Kotlin 可空性、默认值等等。在某些情况下,Moshi 恰好与标准 Java 反射一起工作,您上面的示例就是其中之一。不过这很容易出错,在 Moshi 1.9 中,这些将被拒绝,并且需要生成的适配器或 kotlin-reflect。

    它是如何工作的

Code gen 是一个注解处理器,用于查找使用@JsonClass(generateAdapter = true) 注解的类。它为每个带注释的类生成一个优化的流适配器。这些适配器本身就是 Kotlin,因此能够利用支持目标类的 Kotlin 语言特性。在运行时,Moshi 使用非常简单的已知名称后缀反射性地查找生成的适配器,这允许这些适配器无需手动注册适配器即可正常工作。

您可以在我发布的博客文章中找到关于 1 和 2 的更多信息:https://www.zacsweers.dev/exploring-moshis-kotlin-code-gen/

    什么时候使用

当您尝试使用 Moshi 序列化 Kotlin 类而不使用您自己的自定义适配器时,您应该使用 moshi-kotlin 或 code gen。反射将没有构建时间开销,但在运行时会慢得多,同时由于 kotlin-reflect 还会产生较大的二进制大小成本,并且无法安全地进行混淆。代码生成会产生构建时间成本,但在运行时速度非常快,二进制大小成本最小,而且大部分是混淆安全的。由您决定哪一个更适合您的用例!您还可以组合使用,例如调试构建中的反射和仅用于发布构建的代码生成。

【讨论】:

谢谢!我有一个问题 wrt @user2340612 的回答:如果我理解正确,使用 @JsonClass(generateAdapter = true) 基本上可以避免“ClassJsonAdapter”,从而避免反射? 对。它向 Moshi 表明应该使用生成的适配器。 Moshi 代码生成注释处理器也使用它来指示哪些类应该为它们生成适配器。 @ZacSweers 只是想知道您答案的第三部分。它也有助于de-serialize json 对吗?【参考方案2】:

早期版本的 Moshi 不支持“codegen”,因此它们完全依赖于反射(即在运行时内省类的能力)。但是,对于需要非常高性能的应用程序来说,这可能是一个问题,因此他们添加了利用注释处理的“代码生成”功能。基本上这允许在编译时生成代码,因此它们可以在不使用反射的情况下执行序列化/反序列化。

为了启用代码生成功能,您还需要启用 Kapt,它是 Kotlin 注释处理器,否则不会发生注释处理。

Here 您将了解如何启用和配置 Kapt,here 您将了解如何设置 Moshi 代码生成依赖项。


编辑

在您添加到问题的代码的 sn-p 中,Moshi 使用 ClassJsonAdapter 序列化您的对象。该适配器利用反射来查找所有字段并创建 JSON 字符串(您可以看到该类从 java.lang.reflect 导入内容)。

另一方面,Moshi 代码生成为您的类生成一个 JsonAdapter。例如,让 Moshi 为您的 Person 类创建适配器将生成以下内容:

// Code generated by moshi-kotlin-codegen. Do not edit.
import com.squareup.moshi.JsonAdapter
import com.squareup.moshi.JsonDataException
import com.squareup.moshi.JsonReader
import com.squareup.moshi.JsonWriter
import com.squareup.moshi.Moshi
import java.lang.NullPointerException
import kotlin.String

class PersonJsonAdapter(moshi: Moshi) : JsonAdapter<Person>() 
    private val options: JsonReader.Options = JsonReader.Options.of("name")

    private val stringAdapter: JsonAdapter<String> =
            moshi.adapter<String>(String::class.java, kotlin.collections.emptySet(), "name")

    override fun toString(): String = "GeneratedJsonAdapter(Person)"

    override fun fromJson(reader: JsonReader): Person 
        var name: String? = null
        reader.beginObject()
        while (reader.hasNext()) 
            when (reader.selectName(options)) 
                0 -> name = stringAdapter.fromJson(reader) ?: throw JsonDataException("Non-null value 'name' was null at $reader.path")
                -1 -> 
                    // Unknown name, skip it.
                    reader.skipName()
                    reader.skipValue()
                
            
        
        reader.endObject()
        var result = Person(
                name = name ?: throw JsonDataException("Required property 'name' missing at $reader.path"))
        return result
    

    override fun toJson(writer: JsonWriter, value: Person?) 
        if (value == null) 
            throw NullPointerException("value was null! Wrap in .nullSafe() to write nullable values.")
        
        writer.beginObject()
        writer.name("name")
        stringAdapter.toJson(writer, value.name)
        writer.endObject()
    

因此,当您请求Person 的适配器时,Moshi 将使用生成的PersonJsonAdapter 而不是ClassJsonAdapter

奖金

Moshi uses reflection to instantiate the generated adapter(然后被缓存,以便下次您请求同一类的适配器时可以重用它),因此如果您想使用,则根本不需要添加任何额外的代码代码生成功能。

【讨论】:

Json 序列化/反序列化无需反射和代码生成即可工作。我的问题是代码生成的优势或真正用途是什么? @JigneshShah 请检查我更新的答案。我希望现在更清楚了:) 感谢您非常详细的回复!如果我理解正确,使用@JsonClass(generateAdapter = true) 基本上可以避免“ClassJsonAdapter”,从而避免反射? @JigneshShah 基本上是的,但仅适用于带有 @JsonClass(generateAdapter=true) 注释的类。换句话说,如果你有 3 个类并且只有 1 个有那个注解,那么 Moshi 将只为后者生成一个适配器,而其他 2 个类将使用反射进行序列化/反序列化 @JigneshShah 不,codegen 只会为带注释的类生成适配器,因此您需要手动注释内部和/或其他引用的类

以上是关于Moshi 的 Kotlin 代码生成器有啥用?的主要内容,如果未能解决你的问题,请参考以下文章

Kotlin 1.5 新特性:密封接口有啥用?

当我们在webservice.asmx.cs中编写所有代码时,webservice.asmx有啥用?

在 Kotlin 中使用 Moshi 和 Retrofit 解析具有增量对象名称的 JSON

Moshi 忽略 Kotlin 中的字段

Moshi + Kotlin + SealedClass

JOOQ自动生成的类中字段的SQLDataType有啥用