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
IN_PROGRESS to one of either
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 —
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:
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.
Now, for the contents. Moshi usually implicitly convert the string
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:
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.
JsonReaderis 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
JsonAdapteris 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:
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
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