Upgrading to Moshi

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

Upgrading to Moshi

Moshi is the successor to Gson. We will briefly contrast Moshi to Gson and explain the advantages of Moshi's API and implementation. We 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 easy. Finally, we will go through how to use Moshi effectively with Retrofit and Auto-Value-Moshi and some tricks to employ when upgrading.
Bonus: Let's talk about Kotlin Support!

627ce26ea490d993a49d6b263768ca67?s=128

Eric Cochran

November 05, 2017
Tweet

Transcript

  1. Upgrading to Moshi Eric Cochran Droidcon San Francisco November 5,

    2017
  2. Moshi?

  3. Moshi? JSON serialization library for Java with a streaming and

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

    object-mapping API and Kotlin
  5. Moshi? JSON serialization library for Java with a streaming and

    object-mapping API Gson 3 Lite and Kotlin
  6. Why update from Gson? Inactive

  7. Why update from Gson? Inactive Too lenient

  8. Why update from Gson? Inactive Too lenient Large API

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

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

    exceptions (Moshi has IOExceptions and JsonDataExceptions) ~188KB, 1345 methods (Moshi is 112KB, 759 methods)
  11. Why update from Gson? Inactive Too lenient Large API Inconsistent

    exceptions (Moshi has IOExceptions and JsonDataExceptions) ~188KB, 1345 methods (Moshi is 112KB, 759 methods) Moshi optimizations and API niceties…
  12. Moshi optimizations Share buffer segments with other Okio users

  13. Moshi optimizations Share buffer segments with other Okio users 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 renaming @SerializedName("the_name")

  17. Reflective field renaming @SerializedName("the_name") @Json(name = "the_name")

  18. Reflective field renaming @SerializedName("the_name") @Json(name = "the_name") Prefer using the

    domain’s naming convention
  19. Streaming API com.google.gson.stream.JsonReader com.google.gson.stream.JsonWriter

  20. Streaming API com.google.gson.stream.JsonReader com.google.gson.stream.JsonWriter It’s the same! com.squareup.moshi.JsonReader com.squareup.moshi.JsonWriter

  21. Streaming API com.google.gson.stream.JsonReader com.google.gson.stream.JsonWriter It’s the same! com.squareup.moshi.JsonReader com.squareup.moshi.JsonWriter Bonus:

    JsonReader.Options
  22. Streaming API com.google.gson.stream.JsonReader com.google.gson.stream.JsonWriter It’s the same! com.squareup.moshi.JsonReader com.squareup.moshi.JsonWriter Bonus:

    JsonReader.Options Bonus: JsonReader.setFailOnUnknown
  23. JsonReader.Options Prepare strings ahead of time: Options.of("key1", "key2") Read out

    directly from the source: JsonReader.selectName(options), JsonReader.selectString(options) Returns index of string in Options
  24. setFailOnUnknown JsonReader.setFailOnUnknown(true) Fail when JsonReader.skipValue() is called to ensure you

    are not missing any JSON data while debugging
  25. Object Mapping TypeAdapter

  26. Object Mapping TypeAdapter JsonAdapter

  27. Object Mapping TypeAdapter JsonAdapter No document-level API like Gson.fromJson

  28. Object Mapping TypeAdapter JsonAdapter No document-level API like Gson.fromJson Gson.getAdapter(TypeToken)

  29. Object Mapping TypeAdapter JsonAdapter No document-level API like Gson.fromJson Gson.getAdapter(TypeToken)

    Moshi.adapter(Type)
  30. Object Mapping TypeAdapter JsonAdapter No document-level API like Gson.fromJson Gson.getAdapter(TypeToken)

    Moshi.adapter(Type) Cache your adapters! https://publicobject.com/2016/03/24/reflection-machines/
  31. Object Mapping without Footgun Platform types require user-registered JsonAdapters moshi.adapter(java.util.Date.class)

    moshi.adapter(java.util.ArrayList.class) moshi.adapter(android.graphics.Point.class)
  32. Object Mapping JsonAdapter wrappers serializeNulls() nullSafe() lenient() indent(String) failOnUnknown()

  33. Object Mapping JsonSerializer JsonDeserializer

  34. Object Mapping JsonSerializer JsonDeserializer These don’t stream!

  35. Object Mapping TypeAdapter TypeAdapterFactory

  36. Object Mapping TypeAdapter TypeAdapterFactory JsonAdapter JsonAdapter.Factory

  37. Object Mapping TypeToken

  38. Object Mapping TypeToken java.lang.reflect.Type com.squareup.moshi.Types factory methods

  39. Object Mapping TypeToken java.lang.reflect.Type com.squareup.moshi.Types factory methods TypeToken.getParameterized(List.class, String.class)

  40. Object Mapping TypeToken java.lang.reflect.Type com.squareup.moshi.Types factory methods TypeToken.getParameterized(List.class, String.class) Types.newParameterizedType(List.class,

    String.class)
  41. Object Mapping Unknown Enums enum Exercise { run, jump, walk

    } exerciseTypeAdapter.fromJson("jog") == null
  42. Object Mapping Unknown Enums enum Exercise { run, jump, walk

    } exerciseTypeAdapter.fromJson("jog") == null exerciseJsonAdapter.fromJson("jog") throws JsonDataException
  43. Object Mapping Unknown Enums enum Exercise { run, jump, walk

    } exerciseTypeAdapter.fromJson("jog") == null exerciseJsonAdapter.fromJson("jog") throws JsonDataException EnumWithDefaultValueJsonAdapter https://goo.gl/85U7Pu Moshi API for fallback enums?
  44. Object Mapping with JsonQualifiers Special-case type qualifiers class Data {

    @JsonAdapter(WrappedStringTypeAdapter.class) String string; }
  45. Object Mapping with JsonQualifiers Special-case type qualifiers class Data {

    @JsonAdapter(WrappedStringTypeAdapter.class) String string; } @Retention(RUNTIME) @JsonQualifier @interface WrappedString {} class Data { @WrappedString String string; }
  46. Object Mapping with JsonQualifiers class WrappedStringTypeAdapter extends TypeAdapter<String> { String

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

    fromJson(JsonReader reader) throws IOException { reader.beginObject(); String string = reader.nextString(); reader.endObject(); return string; } }
  48. 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) {...} } } }
  49. 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) {...} } } }
  50. 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) {...} }
  51. Even Easier JsonAdapters @FromJson Foo fromJson(JsonReader reader) @FromJson Foo fromJson(JsonReader

    reader, JsonAdapter<any> delegate, <any more delegates>) // Bar is a type that can already be deserialized. @FromJson Foo fromJson(Bar value)
  52. Even Easier JsonAdapters @ToJson void toJson(JsonWriter writer, Foo value) @ToJson

    void toJson(JsonWriter writer, JsonAdapter<any> delegate, <any more delegates>) @ToJson void toJson(JsonWriter writer, Foo value, JsonAdapter<any> delegate, <any more delegates>) // Bar is a type that can already be serialized. @FromJson Bar fromJson(Foo value)
  53. Polymorphic Types class Animal { String type; } List<Animal> animals

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

    = animalAdapter.fromJson(source) class Dog extends Animal { String bark; } class Cat extends Animal { int meals; }
  55. Polymorphic Types JsonElement (JsonObject, JsonArray, String, primitives)

  56. Polymorphic Types JsonElement (JsonObject, JsonArray, String, primitives) Object (Map<String, Object>,

    List<Object>, String, primitives)
  57. 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); // ... } }
  58. 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); // ... } }
  59. 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); // ... } }
  60. 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); // ... } }
  61. Polymorphic Types RuntimeTypeAdapterFactory

  62. Polymorphic Types RuntimeTypeAdapterFactory ???

  63. Updating Piecemeal

  64. 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. }
  65. 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); } }
  66. 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(); } }
  67. Kotlin

  68. KotlinJsonAdapter data class TrainStation(val name: String, val cost: Int =

    250) { "name": "Reading" }
  69. KotlinJsonAdapter data class TrainStation(val name: String, val cost: Int =

    250) { "name": null } { }
  70. KotlinJsonAdapter data class TrainStation(val name: String = "Reading", val cost:

    Int = 250) { "name": null } { }
  71. KotlinJsonAdapter data class TrainStation(val name: String, val cost: Int =

    250, val trains: List<Train>) { "name": "Reading", "cost": "1000", "trains": [ {…}, null ] } val train = station.trains[1]
  72. KotlinJsonAdapter val moshi = Moshi.Builder() // Add any other JsonAdapter

    factories. .add(KotlinJsonAdapterFactory()) .build()
  73. KotlinJsonAdapter val moshi = Moshi.Builder() // Add any other JsonAdapter

    factories. .add(KotlinJsonAdapterFactory()) .build() fun withKotlinJsonAdapterFactory(moshi: Moshi) = moshi.newBuilder().add(KotlinJsonAdapterFactory()).build()
  74. 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

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