Upgrading to Moshi

627ce26ea490d993a49d6b263768ca67?s=47 Eric Cochran
September 05, 2017

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.

627ce26ea490d993a49d6b263768ca67?s=128

Eric Cochran

September 05, 2017
Tweet

Transcript

  1. Upgrading to Moshi Eric Cochran Droidcon Berlin September 5, 2017

  2. What is Moshi? JSON serialization library for Java with a

    streaming and object-mapping API.
  3. What is Moshi? JSON serialization library for Java with a

    streaming and object-mapping API. Gson 2
  4. What is Moshi? JSON serialization library for Java with a

    streaming and object-mapping API. Gson 2 => “Gson Lite”
  5. Why update from Gson?

  6. Why update from Gson? Gson: - Inactive

  7. Why update from Gson? Gson: - Inactive - Too lenient

  8. Why update from Gson? Gson: - Inactive - Too lenient

    - Large API
  9. Why update from Gson? Gson: - Inactive - Too lenient

    - Large API - Inconsistent exceptions (Moshi: IOExceptions and JsonDataExceptions)
  10. Why update from Gson? Gson: - Inactive - Too lenient

    - Large API - Inconsistent exceptions (Moshi: IOExceptions and JsonDataExceptions) - ~188KB, 1345 methods (Moshi: 112KB, 759 methods)
  11. 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…)
  12. Moshi optimizations Share buffer segments with other Okio users.

  13. Moshi optimizations Avoid allocating strings while deserializing. JsonReader.selectName(Options)

  14. How to Upgrade

  15. FieldNamingPolicy https://publicobject.com/2016/01/20/strict-naming-conventions-are-a-liability/

  16. Reflective field naming policy @SerializedName(“the_name”) => @Json(name = “the_name”)

  17. Reflective field naming policy @SerializedName(“the_name”) => @Json(“the_name”) Prefer using the

    domain’s naming convention.
  18. Streaming API It’s the same! com.google.gson.stream.JsonReader => com.squareup.moshi.JsonReader com.google.gson.stream.JsonWriter =>

    com.squareup.moshi.JsonWriter
  19. 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
  20. 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.
  21. setFailOnUnknown JsonReader.setFailOnUnkown(true) Fail when JsonReader.skipValue() is called to ensure you

    are not missing any JSON data while debugging.
  22. Object Mapping TypeAdapter => JsonAdapter

  23. Object Mapping TypeAdapter => JsonAdapter No document-level API like Gson.fromJson

  24. Object Mapping TypeAdapter => JsonAdapter No document-level API like Gson.fromJson

    Gson.getAdapter(Type) => Moshi.adapter(Type)
  25. 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/
  26. 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)
  27. Object Mapping JsonAdapter wrappers: serializeNulls(), nullSafe(), lenient(), indent(String), failOnUnknown()

  28. Object Mapping No JsonSerializer, JsonDeserializer! These did not stream. TypeAdapter,

    TypeAdapterFactory => JsonAdapter, JsonAdapter.Factory
  29. 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)
  30. Object Mapping: Unknown Enums enum Exercise { RUN, JUMP, WALK

    } Gson: exerciseTypeAdapter.fromJson(“jog”) == null Moshi: exerciseJsonAdapter.fromJson(“jog”) => throws JsonDataException
  31. 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?
  32. Object Mapping: JsonQualifier Special-case type qualifiers: class Data { @JsonAdapter(WrappedStringTypeAdapter.class)

    String string; } => @Retention(RUNTIME) @JsonQualifier @interface WrappedString {} class Data { @WrappedString String string }
  33. Object Mapping: JsonQualifier class WrappedStringTypeAdapter extends TypeAdapter<String> { String read(JsonReader

    reader) throws IOException { reader.beginObject(); String string = reader.nextString(); reader.endObject(); return string; } }
  34. Object Mapping: JsonQualifiers class WrappedStringAdapter { @FromJson @WrappedString String fromJson(JsonReader

    reader) { reader.beginObject(); String string = reader.nextString(); reader.endObject(); return string; } }
  35. 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) {...} } } }
  36. 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) {...} } } }
  37. 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) {...} } } }
  38. 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) {...} }
  39. 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.
  40. 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.
  41. Advanced: Polymorphic types class Animal { String type; } List<Animal>

    animals = animalAdapter.fromJson(source)
  42. Polymorphic types class Animal { String type; } List<Animal> animals

    = animalAdapter.fromJson(source) class Dog extends Animal { String bark; } class Cat extends Animal { int meals; }
  43. Polymorphic types Gson: JsonElement (JsonObject, JsonArray, String, primitives) Moshi: Object

    (Map<String, Object>, List<Object>, String, primitives)
  44. 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); // ... } }
  45. 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); // ... } }
  46. 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); // ... } }
  47. 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); // ... } }
  48. Polymorphic types Gson: RuntimeTypeAdapterFactory Moshi: ???

  49. Updating Piecemeal

  50. 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. }
  51. 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); } }
  52. 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(); } }
  53. Resources medium.com/square-corner-blog/moshi-another-json-processor-624f8741f703 “A Few Ok Libraries” https://youtu.be/WvyScM_S88c stackoverflow.com/questions/tagged/moshi

  54. github.com/square/moshi @Eric_Cochran github.com/NightlyNexus blog.nightlynexus.com