I've recently started using Moshi for my Android app and I'm curious to know more about what the annotation @JsonClass(generateAdapter = true)
really does.
Example data class:
data class Person(
val name: String
)
I'm able to serialise/de-serialise this class as follows:
val moshi: Moshi = Moshi.Builder().build()
moshi.adapter(Person::class.java).toJson(Person())
I'm not using the @JsonClass annotation here and hence codegen won't kick in.
My question is, why and when do I need to use @JsonClass(generateAdapter = true)
Earlier versions of Moshi didn't support "codegen", so they completely relied on reflection (i.e., the ability to introspect classes at runtime). However, that could be a problem for applications that require very high performance so they've added a "codegen" capability that takes advantage of annotation processing. Basically that allows generating code at compilation time, so they can perform serialisation/deserialisation without using reflection.
In order to enable the code generation functionality you also need to enable Kapt, which is the Kotlin annotation processor, otherwise no annotation processing will happen.
Here you'll find how to enable and configure Kapt, and here you'll find how to set up Moshi codegen dependencies.
In the snippet of code you added to your question, Moshi uses ClassJsonAdapter to serialise your object. That adapter makes use of reflection to find all the fields and create the JSON string (you can see that class imports stuff from java.lang.reflect
).
On the other hand, Moshi code gen generates a JsonAdapter for your class for you. For instance, letting Moshi create an adapter for your Person
class will generate the following:
// 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()
}
}
As a result, when you ask for an adapter for Person
, Moshi will use the generated PersonJsonAdapter
instead of the ClassJsonAdapter
.
Moshi uses reflection to instantiate the generated adapter (which then gets cached, so that it gets reused next time you ask for an adapter for the same class), so that you don't need to add any extra code at all if you want to use the codegen functionality.