Share
Embed
Upgrading to Moshi
Moshi is the successor to Gson. I will briefly contrast Moshi to Gson and explain the advantages of Moshi's API and implementation. I will cover the power of the streaming API for complex use cases, like polymorphic deserialization, and how Okio's types make building off of Moshi fun and easy. Finally, we will go through how to use Moshi effectively with Retrofit and Auto-Value-Moshi and some tricks to employ when upgrading.
Eric Cochran
September 05, 2017
Transcript
54 slides
-
What is Moshi? JSON serialization library for Java with a
streaming and object-mapping API. -
What is Moshi? JSON serialization library for Java with a
streaming and object-mapping API. Gson 2 -
What is Moshi? JSON serialization library for Java with a
streaming and object-mapping API. Gson 2 => “Gson Lite” -
Why update from Gson? Gson: - Inactive - Too lenient
- Large API - Inconsistent exceptions (Moshi: IOExceptions and JsonDataExceptions) -
Why update from Gson? Gson: - Inactive - Too lenient
- Large API - Inconsistent exceptions (Moshi: IOExceptions and JsonDataExceptions) - ~188KB, 1345 methods (Moshi: 112KB, 759 methods) -
Why update from Gson? Gson: - Inactive - Too lenient
- Large API - Inconsistent exceptions (Moshi: IOExceptions and JsonDataExceptions) - ~188KB, 1345 methods (Moshi: 112KB, 759 methods) Moshi optimizations and API niceties (continued…) -
Reflective field naming policy @SerializedName(“the_name”) => @Json(“the_name”) Prefer using the
domain’s naming convention. -
Streaming API It’s the same! com.google.gson.stream.JsonReader => com.squareup.moshi.JsonReader com.google.gson.stream.JsonWriter =>
com.squareup.moshi.JsonWriter Moshi Bonus: JsonReader.Options Moshi Bonus: JsonReader.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.setFailOnUnkown(true) 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 TypeAdapter => JsonAdapter No document-level API like Gson.fromJson
Gson.getAdapter(Type) => Moshi.adapter(Type) Cache your adapters! https://publicobject.com/2016/03/24/reflection-machines/ -
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) -
Object Mapping No JsonSerializer, JsonDeserializer! These did not stream. TypeAdapter,
TypeAdapterFactory => JsonAdapter, JsonAdapter.Factory -
Object Mapping TypeToken => com.squareup.moshi.Types factory methods Moshi prefers plain
Java’s java.lang.reflect.Type. TypeToken.getParameterized(List.class, String.class) => Types.newParameterizedType(List.class, String.class) -
Object Mapping: Unknown Enums enum Exercise { RUN, JUMP, WALK
} Gson: exerciseTypeAdapter.fromJson(“jog”) == null Moshi: exerciseJsonAdapter.fromJson(“jog”) => throws JsonDataException -
Object Mapping: Unknown Enums enum Exercise { RUN, JUMP, WALK
} Gson: exerciseTypeAdapter.fromJson(“jog”) == null Moshi: exerciseJsonAdapter.fromJson(“jog”) => throws JsonDataException EnumWithDefaultValueJsonAdapter: https://goo.gl/85U7Pu API in Moshi to have fallback enums? -
Object Mapping: JsonQualifier Special-case type qualifiers: class Data { @JsonAdapter(WrappedStringTypeAdapter.class)
String string; } => @Retention(RUNTIME) @JsonQualifier @interface WrappedString {} class Data { @WrappedString String string } -
Object Mapping: JsonQualifier class WrappedStringTypeAdapter extends TypeAdapter<String> { String read(JsonReader
reader) throws IOException { reader.beginObject(); String string = reader.nextString(); reader.endObject(); return string; } } -
Object Mapping: JsonQualifiers class WrappedStringAdapter { @FromJson @WrappedString String fromJson(JsonReader
reader) { reader.beginObject(); String string = reader.nextString(); reader.endObject(); return string; } } -
Object Mapping: Easier JsonAdapters class PointJsonAdaperFactory implements JsonAdapter.Factory { JsonAdapter<?>
create(Type type, 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) {...} } } } -
Object Mapping: Easier JsonAdapters class PointJsonAdaperFactory implements JsonAdapter.Factory { JsonAdapter<?>
create(Type type, 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) {...} } } } -
Object Mapping: Easier JsonAdapters class PointJsonAdaperFactory implements JsonAdapter.Factory { JsonAdapter<?>
create(Type type, 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, Point value) {...} } } } -
Object Mapping: Easier JsonAdapters class PointJsonAdaperFactory implements JsonAdapter.Factory { JsonAdapter<?>
create(Type type, 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, Point value) {...} } } } => class PointJsonAdapter { @FromJson Point fromJson(JsonReader reader) {...} @ToJson void toJson(JsonWriter writer, Point value) {...} } -
Even Easier JsonAdapters @FromJson Foo fromJson(JsonReader reader) @FromJson Foo fromJson(JsonReader
jsonReader, JsonAdapter<any> delegate, <any more delegates>) @FromJson Foo fromJson(Bar value) // Bar is already a type that can be deserialized. -
Even Easier JsonAdapters @FromJson Foo fromJson(JsonReader reader) @FromJson Foo fromJson(JsonReader
jsonReader, 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 void toJson(JsonWriter writer, T value, JsonAdapter<any> delegate, <any more delegates>) @ToJson Bar toJson(Foo value) // Bar is already a type that can be serialized. -
Advanced: Polymorphic types class Animal { String type; } List<Animal>
animals = animalAdapter.fromJson(source) -
Polymorphic types class Animal { String type; } List<Animal> animals
= animalAdapter.fromJson(source) class Dog extends Animal { String bark; } class Cat extends Animal { int meals; } -
Polymorphic types Gson: JsonElement (JsonObject, JsonArray, String, primitives) Moshi: Object
(Map<String, Object>, List<Object>, String, primitives) -
Polymorphic types Gson: class AnimalTypeAdapter extends TypeAdapter<Animal> { TypeAdapter<JsonElement> elementAdapter;
TypeAdapter<Cat> catAdapter; TypeAdapter<Dog> dogAdapter; Animal read(JsonReader in) { JsonObject value = elementAdapter.read(in).getAsJsonObject(); // Inspect the value. Decide what TypeAdapter to delegate to. if (value.get(“type”).getAsString().equals(“cat”)) return catAdapter.fromJsonTree(value); // ... } } -
Polymorphic types Gson: class AnimalTypeAdapter extends TypeAdapter<Animal> { TypeAdapter<JsonElement> elementAdapter;
TypeAdapter<Cat> catAdapter; TypeAdapter<Dog> dogAdapter; Animal read(JsonReader in) { JsonObject value = elementAdapter.read(in).getAsJsonObject(); // Inspect the value. Decide what TypeAdapter to delegate to. if (value.get(“type”).getAsString().equals(“cat”)) return catAdapter.fromJsonTree(value); // ... } } -
Polymorphic types Moshi: class AnimalJsonAdapter extends JsonAdapter<Animal> { JsonAdapter<Cat> catAdapter;
JsonAdapter<Dog> dogAdapter; Animal fromJson(JsonReader in) { Map<String, Object> value = (Map<String, Object>) in.readJsonValue(); // Inspect the value. Decide what JsonAdapter to delegate to. if (value.get(“type”).equals(“cat”)) return catAdapter.fromJsonValue(value); // ... } } -
Polymorphic types Moshi: class AnimalJsonAdapter extends JsonAdapter<Animal> { JsonAdapter<Cat> catAdapter;
JsonAdapter<Dog> dogAdapter; Animal fromJson(JsonReader in) { Map<String, Object> value = (Map<String, Object>) in.readJsonValue(); // Inspect the value. Decide what JsonAdapter to delegate to. if (value.get(“type”).equals(“cat”)) return catAdapter.fromJsonValue(value); // ... } } -
Retrofit AnnotatedConverterFactory https://goo.gl/nhLt8z @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. } -
Auto-Value-Moshi https://github.com/rharter/auto-value-moshi @AutoValue abstract class Data { abstract String string();
static JsonAdapter<Data> jsonAdapter(Moshi moshi) { return new AutoValue_Data.MoshiJsonAdapter(moshi); } } -
Auto-Value-Moshi https://github.com/rharter/auto-value-moshi @MoshiAdapterFactory abstract class MyJsonAdapterFactory implements JsonAdapter.Factory { static
JsonAdapter.Factory create() { return new AutoValueMoshi_MyJsonAdapterFactory(); } }