This article has been translated by Gemini.
In this Notes article, I roughly jotted down notes in English from the content of the English session video/podcast. This article is intended to be read as a reference while watching the video. Please note that these are just rough notes or transcript-like content. I hope this article helps you even a little when you watch the actual video. (I would be happy if you could let me know if there are any errors or typos.)
This article is based on droidcon NYC 2017 - Upgrading to Moshi - YouTube.
Currently, I personally feel that Gson is the most popular library for JSON serialization/deserialization in Android. However, this session video introduces what Moshi is, why Moshi is superior to Gson (or about APIs that Gson provides but Moshi currently doesn’t), and the merits and methods of replacing Gson with Moshi. (Note: There is almost no explanation about moshi-kotlin in this session video. It seems to focus more on explaining the Moshi JSON parser library itself rather than talking about Kotlin.)

Upgrading to Moshi#
What is Moshi?#
JSON serialization library for Java with a streaming and object-mapping API.
Moshi is kind of “Gson 3”; it is kind of Gson 2 but kind of like “Gson Lite”
It takes great things from Gson API and removes extra part of Gson API that not many people are using
Why update from Gson?#
Gson often contains breaking API changes
Application using Gson does not often update Gson dependnecy
If the code already works with Gson then not necessarily need to upgrade; you are not going to obtain amazing performance optimization by switching from Gson to Moshi
Why update from Gson: Gson#
In Inactive development
Breaking changes
Not many people update to use the latest version
Too lenient
“Platform type issue”
“Date” type adapter
Implementation change in the platform affects your adapter that has relied upon the previous version of the platform implementation
Large API
Inconsistent exceptions(e.g. IOException may occur when it actually should be data exception).
~188KB, 1345 methods (Moshi: 112KB, 759 methods)
Why update from Gson: Moshi optimizations#
Share buffer segments with other Okio users.
- If you use OkHttp or Retrofit or any other libraries that rely on Okio
Avoid allocating strings while deserializing.
JsonReader.selectName(Options)allows you to pre-allocate memories.
How to upgrade?#
FieldNamingPolicy#
Waste of computation time
Bad for code search
Bad for readability
=> Defining model classes as actual JSON response may be clearer. (Even using sneak_cases as wrigin sneak_cases for layout_ids in Android)
Reflective field naming policy#
@SerializedName("the_name")=>@Json(name="the_name")
Streaming API#
It’s the same!
com.google.gson.stream.JsonReader=>com.squareup.moshi.JsonReadercom.google.gson.stream.JsonWriter=>com.squareup.moshi.JsonWriterMoshi bonus
JsonReader.OptionsJsonReader.setFailOnUnknown
JsonReader.Options#
Prepare strings ahead of time:
Options.of("key1", "key2")Read out directly from the input source:
JsonReader.selectName(options), JsonReader.selectString(options)returns index of string in Options.
setFailOnUnknown#
JsonReader.setFailOnUnknown(true)Useful for debugging, not for production app
Fail when
JsonReader.skipValue()is called to ensure you are not missing any JSON data while debugging
Object Mapping#
TypeAdapter => JsonAdapter
No document-level API like
Gson.fromJson()Gson.getAdapter(Type)=>Moshi.adapter(Type)Object Mapping without bad leniency
Platform types require explicitly registered JsonAdapters.
moshi.adapter(java.util.Date.class)moshi.adapter(java.util.ArrayList.class)moshi.adapter(android.graphics.Point.class)
JsonAdapter wrappers:
serializeNulls(),nullSafe(),lenient(),indent(String),failOnUnknown()
TypeToken => com.squareup.moshi.Types factory methods
Moshi preferes plain Java’s java.lang.reflect.Type. => TypeToken.getParameterized(List.class, String.class) => Types.newParameterizedType(List.class, String.class)
Unknown Enums#
enum Exercise { RUN, JUMP, WALK }Gson: exerciseTypeAdapter.fromJson("jog") => returns null
Moshi: exerciseJsonAdapter.fromJson("jog") => throws JsonDataException
EnumWithDefaultValueJsonAdapter => API in Moshi to have fallback enums?
JsonQualifier#
Special-case type qualifiers:
class Data { @JsonAdapter(WrappedStringTypeAdapter.class) String string; }=>
@Retention(RUNTIME) @JsonQualifier @interface WrappedString {}
class Data { @WrappedString String string }WrappedStringTypeAdapter.java
class WrappedStringTypeAdapter extends TypeAdapter<String> {
String read(JsonReader reader) throws IOException {
reader.beginObject();
String string = reader.nextString();
reader.endObject();
return string;
}
}Easier JsonAdapters#
Traditional JsonAdapter.Factory code implementation looks like this:
class PointJsonAdapterFactory implements JsonAdapter.Factory {
JsonAdapter<?> create(Type typem Set<? extends Annotation> annotations, Moshi moshi) {
if (Types.getRawType(types) != Point.class) return null;
return new JsonAdapter<Point> {
Point fromJson(JsonReader reader) { ... }
void toJson(JsonWriter writer) { ... }
}
}
}A lot of boilerplates
The code tends to be error-prone codes
- Often the code is not tested
Blow is the easier version:
class PointJsonAdapter {
@FromJson Point fromJson(JsonReader reader) { ... }
@ToJson void toJson(JsonWriter writer, Point value) { ... }
}It uses reflection API; when you add this object to Moshi.Builder then Moshi will create the factory for you
Here is the even easier ones:
@FromJson Foo fromJson(JsonReader reader)
@FromJson Foo fromJson(JsonReader reader, JsonAdapter<any> delegate, <any more delegates>)
@FromJson Foo fromJson(Bar value) // Bar is already a type that can be deserialized
@ToJson void toJson(JsonWriter writer, Foo value)
@ToJson void toJson(JsonWriter writer, JsonAdapter<any> delegate, <any more delegates>)
@ToJson Bar toJson(Foo value) // Foo is already a type that can be serializedAdvanced: Polymorphic types#
class Animal { String type; }
List<Animal> animals = animalAdapter.fromJson(source)Gson: JsonElement(JsonObject, JsonArray,
Gson: RuntimeTypeAdapterFactory Moshi: ???
Updating piecemeal#
Retrofit#
@Moshi for new code
Retrofit retrofit = new Retrofit.Builder().baseUrl(server.url("/"))
.addConverterFactory(new AnnotatedConverterFactory.Builder()
.add(com.example.Moshi.class, moshiConverterFactory)
.add(com.example.Gson.class, gsonConverterFactory)
.build())
.addConverterFactory(gsonConverterFactory) // Fallback
.build();
interface Service {
@GET("/new_endpoint") @com.example.Moshi Call<Foo> newEndpoint();
@GET("/old_endpoint") @Gson Call<Foo> oldEndpoint();
@GET("/old_endpoint") Call<Foo> oldEndpointDefault(); // Will use Fallback converter
}