Moshi的Kotlin码本有什么用?

时间:2019-10-22 10:10:59

标签: json kotlin serialization moshi codegen

我最近开始在我的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注释,因此代码生成将无法启动。

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

2 个答案:

答案 0 :(得分:3)

这是三个问题

  1. 为什么代码生成有用

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

  1. 它如何工作

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

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

  1. 何时使用

任何时候在没有您自己的自定义适配器的情况下,尝试使用Moshi序列化Kotlin类时,都应使用moshi-kotlin或代码gen。反射将没有构建时间的开销,但运行时会慢得多,同时由于Kotlin反射会导致二进制文件的开销很大,并且无法安全地进行混淆。代码生成会增加构建时间,但运行时速度极快,二进制大小的开销最小,并且大多数情况下都是安全的。由您决定,其中哪一个最适合您的用例!您还可以结合使用,例如在调试版本中进行反射和仅针对发行版本进行代码生成。

答案 1 :(得分:1)

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

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

Here您将找到如何启用和配置Kapt,here您将找到如何设置Moshi codegen依赖项。


编辑

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

另一方面,Moshi代码gen 为您的类生成一个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(然后将其缓存,以便下次您请求同一类的适配器时可以重新使用),因此您根本不需要添加任何额外的代码使用代码生成功能。