Moshi, another JSON Processor
It’s rare for a programmer to get to work on the same problem twice. Either the first is good enough and you’re done, or it wasn’t and…
It’s rare for a programmer to get to work on the same problem twice. Either the first is good enough and you’re done, or it wasn’t and you’re foolish to try again. Circumstance and hubris have given me two opportunities to make another attempt.
Guice, a Dependency Injector
When I started at Square in 2012, the Android team was using Google Guice for dependency injection. I’d contributed to Guice and so it was nice to use familiar code at my new job. But there was a problem: Guice was too slow for mobile. Even the newest Gingerbread devices weren’t fast enough.
Dagger, another Dependency Injector
I proposed fixing the problem by switching from the reflection-based Guice to a new codegen-based dependency injector that we’d create. I started the prototype that would become Dagger. It was a lot easier to build something the second time! I knew which features were important and which could be left out. Dagger 1.0 borrowed Guice’s core features and made them simpler and faster.
Google has since taken over Dagger development. They’ve expanded its API and improved its performance.
Gson, a JSON processor
I’d also contributed to Google Gson, which Square uses for JSON encoding and databinding. As with Guice, it’s nice to use something I’ve worked on and am proud of. Gson is fast, secure, and easy to get started with. It has a flexible databinding model that scales up to sophisticated use cases.
But the more I use Gson, the more a few old mistakes annoy me. For example, Gson’s default date format is broken:
Date epoch = new Date(0);
String epochJson = new Gson().toJson(epoch);
// "Dec 31, 1969 7:00:00 PM"
The encoded date doesn’t have a time zone so data are corrupted when the encoder and decoder don’t share a time zone. This frustrating part of this bug is that it’s also dangerous to fix: we don’t know what applications are expecting the current format and we don’t know what will break if we change it. (Gson will read RFC 3339 dates like 1970–01–01T00:00:00Z
by default).
Another wrinkle is how the library responds to bad input. When you use Gson.fromJson()
to convert a string to JSON it’s always lenient. It considers {a=TRUE}
to be equivalent to {"a":true}
. It also returns normally for invalid inputs like the empty string and null. This hides bugs but fixing it could break existing applications.
Introducing Moshi
18 months ago Jake, Scott, and I created Moshi in an attempt to borrow some of Gson’s core ideas and make them simpler and faster. We wanted a library that was small, efficient, and safe by default.
Moshi tries to prevent errors when encoding and can detect errors when decoding. For example, if you add a java.util.UUID
field to one of your model objects Moshi will complain unless you explicitly register an adapter for that type. That way you won’t inadvertently leak JDK implementation details into your JSON data.
IllegalArgumentException: Platform class java.util.UUID requires explicit JsonAdapter to be registered
at com.squareup.moshi.ClassJsonAdapter$1.create()
at com.squareup.moshi.Moshi.adapter()
Similarly, Moshi has a strict mode that fails on unused fields. This is a great option to enable for integration testing!
JsonDataException: Cannot skip unexpected STRING at $.fullNane
at com.squareup.moshi.JsonUtf8Reader.skipValue()
at com.squareup.moshi.ClassJsonAdapter.fromJson()
at com.squareup.moshi.JsonAdapter.fromJson()
We’ve been quietly completing Moshi’s feature set and improving its performance. With the recent 1.4 release we added support for JSON values which convert your objects into either a byte stream or a memory model.
If you’re building a new application and need JSON, please consider Moshi. It’s ready.