Trello Android not too long ago transformed from utilizing Gson to Moshi for dealing with JSON. It was a bit difficult so I needed to doc the method.
(For context, Trello Android primarily parses JSON. We not often serialize JSON, and thus a lot of the focus right here is on deserializing.)
There have been three primary causes for the change from Gson to Moshi: security, velocity, and dangerous life decisions.
Security – Gson doesn’t perceive Kotlin’s null security and can fortunately place null values into non-null properties. Additionally, default values solely typically work (relying on the constructor setup).
Pace – Loads of benchmarks (1, 2, 3) have demonstrated that Moshi is normally sooner than Gson. After we transformed, we arrange some benchmarks to see how real-world parsing in contrast in our app, and we noticed a 2x-3.5x speedup:

Dangerous life decisions – As an alternative of utilizing Gson to parse JSON into easy fashions, we’d write elaborate, complicated, brittle customized deserializers that had completely an excessive amount of logic in them. Refactoring gave us a chance to appropriate this architectural snafu.
As for why we picked Moshi over rivals (e.g. Kotlin serialization), we typically belief Sq.’s libraries, we have used Moshi up to now for initiatives (each at work and at dwelling) and felt it labored properly. We didn’t do an in-depth research of alternate options.
Step one was to make sure that we may use characteristic flags to modify between utilizing our outdated Gson implementation and the brand new Moshi one. I wrote a JsonInterop
class which, based mostly on the flag, would both parse all JSON responses utilizing Gson or Moshi.
(I opted to keep away from utilizing instruments like moshi-gson-interop as a result of I needed to check whether or not Moshi parsing labored in its entirety. In case you’d relatively have a mixture of Gson and Moshi on the identical time, that library can be helpful.)
Gson offers you alternatives to override the default naming of a key utilizing @SerializedName
. Moshi permits you to do the identical factor with @Json
. That is all properly and good, but it surely appeared very easy to me to make a mistake right here, the place a property is parsed underneath totally different names in Gson vs. Moshi.
Thus, I wrote some unit exams that may confirm that our generated Moshi adapters would have the identical final result as Gson’s parsing. Specifically, I examined…
- …that Moshi may generate an adapter (not essentially an accurate one!) for every class we needed to deserialize. (If it could not, Moshi would throw an exception.)
- …that every area annotated with
@SerializedName
was additionally annotated with@Json
(utilizing the identical key).
Between these two checks, it was straightforward to seek out once I’d made a mistake updating our courses in later steps.
(I can’t embody the supply right here, however principally we used Guava’s ClassPath to collect all our courses, then scan via them for issues.)
Gson lets you parse generic JSON timber utilizing JsonElement (and pals). We discovered this convenient in some contexts like parsing socket updates (the place we wouldn’t know the way, precisely, to parse the response mannequin till after some preliminary processing).
Clearly, Moshi is just not going to be pleased about utilizing Gson’s courses, so we switched to utilizing Map<String, Any?>
(and typically Record<Map<String, Any?>>
) for generic timber of information. Each Gson and Moshi can parse these:
enjoyable <T> fromJson(map: Map<String, Any?>?, clz: Class<T>): T? {
return if (USE_MOSHI) {
moshi.adapter(clz).fromJsonValue(map)
}
else {
gson.fromJson(gson.toJsonTree(map), clz)
}
}
As well as, Gson is pleasant in the direction of parsing by way of Readers, however Moshi is just not. I discovered that utilizing BufferedSource was a superb various, as it may be transformed to a Reader for outdated Gson code.
The simplest adapters for Moshi are those the place you simply slap @JsonClass
on them and name it a day. Sadly, as I discussed earlier, we had a number of unlucky customized deserialization logic in our Gson parser.
It’s fairly straightforward to write a customized Moshi adapter, however as a result of there was a lot customized logic in our deserializers, simply writing a single adapter wouldn’t reduce it. We ended up having to create interstitial fashions to parse the uncooked JSON, then adapt from that to the fashions we’re used to utilizing.
To provide a concrete instance, think about we’ve a knowledge class Foo(val rely: Int)
, however the precise JSON we get again is of the shape:
{
"knowledge": {
"rely": 5
}
}
With Gson, we may simply manually take a look at the tree and seize the rely out of the knowledge
object, however we’ve found that means lies insanity. We would relatively simply parse utilizing easy POJOs, however we nonetheless need to output a Foo ultimately (so we do not have to vary our entire codebase).
To unravel that downside, we’d create new fashions and use these in customized adapter, like so:
@JsonClass(generateAdapter = true) knowledge class JsonFoo(val knowledge: JsonData)
@JsonClass(generateAdapter = true) knowledge class JsonData(val rely: Int)
object FooAdapter {
@FromJson
enjoyable fromJson(json: JsonFoo): Foo {
return Foo(rely = json.knowledge.rely)
}
}
Voila! Now the parser can nonetheless output Foo, however we’re utilizing easy POJOs to mannequin our knowledge. It’s each simpler to interpret and simple to check.
Bear in mind how I mentioned that Gson will fortunately parse null values into non-null fashions? It seems that we have been (sadly) counting on this habits in all kinds of locations. Specifically, Trello’s sockets typically return partial fashions – so whereas we’d usually anticipate, say, a card to return again with a reputation, in some instances it gained’t.
That meant having to observe our crashes for instances the place the Moshi would blow up (on account of a null worth) when Gson can be pleased as a clam. That is the place characteristic flags actually shine, because you don’t need to should push a buggy parser on unsuspecting manufacturing customers!
After fixing a dozen of those bugs, I really feel like I’ve gained a hearty appreciation for non-JSON applied sciences with well-defined schemas like protocol buffers. There are a number of bugs I bumped into that merely wouldn’t have occurred if we had a contract between the server and the consumer.