Using polymorphic json adapter with Moshi
Hey, today we’re gonna learn how to use the polymorphic json adapter with Moshi. Let’s start with some basics around Moshi and its adapters.
Here’s the code if you don’t have time to read everything.
What is Moshi?#
Quoting Moshi’s Github page here,
Moshi is a modern JSON library for Android and Java. It makes it easy to parse JSON into Java objects.
It is a fast and modern library that can parse JSON using both codegen and reflection. It uses Okio and can also use the same buffer as Okhttp for efficiency. If you haven’t used Moshi before, I highly* recommend you to try it.
* Moshi comes with a lesser number of inbuilt adapters so you might have to write some yourself.
What are Adapters?#
Moshi uses adapters for parsing the content of JSON into a primitive or a user-defined type. These adapters do all the heavy lifting and return us the data that we expect. Moshi comes with inbuilt adapters for primitives like Boolean, String, Integers, etc. However, to parse complex types we can either let Moshi handle it for us or we can take the matter into our own hands and create an adapter. We’ll let Moshi handle everything for now but if you’re interested in reading more about manually creating adapters, Harsh has an awesome article for it.
Moshi adapter using code generation#
You can let Moshi create an adapter for you by annotating your class with @JsonClass
and then passing generateAdapter = true
. There’s also an option to add @Json
and specify the name
when there’s a difference between the JSON and the variable. Here’s an example below.
@JsonClass(generateAdapter = true)
class Person(
@Json(name = "full_name") val fullName: String,
val age: Int,
)
There are a lot more things that you can do with Moshi and we’re not gonna go into that but this how we generate an adapter with the code generation method.
* You’ll have to enable kapt
and add the moshi-kotlin-codegen
dependency so that Moshi can generate an adapter for this class for you
Moshi adapter using reflection#
In the case of reflection, there’s really nothing to do except adding a KotlinJsonAdapterFactory()
while creating an instance of Moshi.
var moshi = Moshi.Builder()
// KotlinJsonAdapterFactoy() should be always added in the end
// so that moshi can try other adapters first.
.add(KotlinJsonAdapterFactory())
.build()
Polymorphic Adapters#
So, if Moshi can generate adapters for you both at build time and at runtime, why do you need a polymorphic adapter? And what even is it?
Good questions! The problem we’re trying solve is that Moshi can only generate adapters when exact types are known. In the case of an abstract class, it is impossible for Moshi to know which implementation of the abstract class it should create and that’s when polymorphic adapters come in.
Learning by seeing is best, so let’s start dabbling in code. Suppose we have a base class called Person
like this:
abstract class Person {
abstract val name: String
abstract val age: String
abstract val occupation: String
}
and two subclasses of Person
, Doctor
and Engineer
:
class Doctor(
override val name: String,
override val age: String
) : Person() {
override val occupation: String = "Doctor"
}
class Engineer(
override val name: String,
override val age: String
) : Person() {
override val occupation: String = "Engineer"
}
Now, if you try to parse a JSON body where the occupation
is Doctor or Engineer, we want Moshi to create an instance of Doctor
or Engineer
class respectively. However, Moshi cannot automatically determine this. Let’s help it along.
Creating a polymorphic adapter#
-
First, add the
moshi-adapters
dependency.implementation("com.squareup.moshi:moshi-adapters:<latest version>")
-
Create adapters for your subclasses (
Doctor
andEngineer
) by either one of the methods mentioned above 👆 . -
Now create an adapter for your abstract class (
Person
class in this example)val adapterFactory = PolymorphicJsonAdapterFactory .of(Person::class.java, "occupation") .withSubtype(Doctor::class.java, "Doctor") .withSubtype(Engineer::class.java, "Engineer")
Here’s the main part of our polymorphic adapter. We create an instance of
PolymorphicJsonAdapterFactory
for the typePerson
, and then we specify which key Moshi should use to determine the class it should instantiate. In our case, that’soccupation
.After that, we add all the subclasses of
Person
and specify the values ofoccupation
on which those classes should be selected. In this case, a JSON body like this:{ name: "Dr. Who", age: 18, occupation: "Doctor" }
will generate an instance of the
Doctor
class. -
The final step is to add this adapter to the builder of your
Moshi
instance.val moshi = Moshi.Builder() .add(adapterFactory) .build()
Ending notes#
Moshi is a powerful and fast JSON library that can perform some really cool stuff. It is built and maintained by some of the smartest developers in the Android space so try it out and have fun playing around with it 😃