Creating a Custom Type Adapter for Moshi

In building an app which guides a user through a process, stage by stage, we would probably use an enum to represent it.

For example, consider a process which goes from NOT_STARTED, through IN_PROGRESS to one of either REJECTED or COMPLETED

A representation of the different stage of some process

The logic for moving between these stages is transparent to the app, we show different screens based on which stage a user is in.

But what if the mapping of stages isn’t 1:1 on the client and the server? What if the server actually has multiple different things to do in the IN_PROGRESS part? We’d end up with lots of irrelevant enums — IN_PROGRESS_STEP1, IN_PROGRESS_STEP2

We can do better very easily with Moshi!

But how do we go about this? We need to hook into the deserialisation process.

First, we need to annotate the enum, so Moshi knows what values it’s looking for when deserialising into our enum:

Our stages, annotated

Now we need to create an StageAdapter which will contain our logic for deserialisation, and apply it when we create our Moshi instance. An Adapter is just a class, there’s nothing to inherit from — it’s named with the suffix Adapter purely as a convention which is a throwback to Moshi’s predecessor, Gson.

Applying our new class to our Moshi instance

Now, for the contents. Moshi usually implicitly convert the string in-progress into IN_PROGRESS for us. We want to keep that behaviour, but we also want to check for strings that start with in-progress to see if they match, and in which case, use IN_PROGRESS as well.

We need to define a method, annotated with @FromJson so that Moshi knows to use it. (There is also @ToJson, for serialisation).

There are several valid signatures for a method annotated @FromJson, as follows:

Method signatures permitted for methods annotated with @FromJson

We need to use something with a delegate so we can keep the existing behaviour to deserialise from string to enum. So that makes our decision easy! (Note: if you use a signature that isn’t one of those, you’ll get an exception including the above to tell you what you’ve done wrong, which is very helpful)

We know how the method should look now, so it’s just a matter of figuring out how to use the two params we have.

  • JsonReader is a class that can read an encoded stream of tokens, so we should be able to get our raw string representing the enum’s value from here
  • JsonAdapter is responsible for converting Kotlin values to and from Json, we can use this to keep the existing behaviour of deserialising other enum values

We can achieve this as follows:

An adapter that matches values that start with`in-progress`

We look for a match of our stage, and if we get it, we return IN_PROGRESS. If not, we let Moshi do its thing as usual, and deserialise those strings to whatever Stage they might be.

Note: there are two, similar methods on JsonAdapter — fromJsonValue() and fromJson(). The difference is that fromJson() expects a JSON content, with JSON escaping and delimiters, where as we have something which is already parsed.

Edit: since publishing this post, I have contributed this example to Moshi’s recipes: https://github.com/square/moshi/pull/352

Show your support

Clapping shows how much you appreciated Emma Guy’s story.