Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Upgrading to Moshi

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!

Eric Cochran

November 05, 2017
Tweet

More Decks by Eric Cochran

Other Decks in Programming

Transcript

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

    object-mapping API Gson 3 Lite and Kotlin
  2. Why update from Gson? Inactive Too lenient Large API Inconsistent

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

    exceptions (Moshi has IOExceptions and JsonDataExceptions) ~188KB, 1345 methods (Moshi is 112KB, 759 methods)
  4. 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…
  5. Moshi optimizations Share buffer segments with other Okio users Avoid

    allocating strings while deserializing JsonReader.selectName(Options)
  6. 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
  7. 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/
  8. 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)
  9. Object Mapping Unknown Enums enum Exercise { run, jump, walk

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

    } exerciseTypeAdapter.fromJson("jog") == null exerciseJsonAdapter.fromJson("jog") throws JsonDataException
  11. 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?
  12. Object Mapping with JsonQualifiers Special-case type qualifiers class Data {

    @JsonAdapter(WrappedStringTypeAdapter.class) String string; }
  13. 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; }
  14. 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; } }
  15. Object Mapping with JsonQualifiers class WrappedStringAdapter { @FromJson @WrappedString String

    fromJson(JsonReader reader) throws IOException { reader.beginObject(); String string = reader.nextString(); reader.endObject(); return string; } }
  16. 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) {...} } } }
  17. 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) {...} } } }
  18. 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) {...} }
  19. 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)
  20. 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)
  21. Polymorphic Types class Animal { String type; } List<Animal> animals

    = animalAdapter.fromJson(source) class Dog extends Animal { String bark; } class Cat extends Animal { int meals; }
  22. 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); // ... } }
  23. 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); // ... } }
  24. 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); // ... } }
  25. 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); // ... } }
  26. 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. }
  27. 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); } }
  28. 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]
  29. KotlinJsonAdapter val moshi = Moshi.Builder() // Add any other JsonAdapter

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

    factories. .add(KotlinJsonAdapterFactory()) .build() fun withKotlinJsonAdapterFactory(moshi: Moshi) = moshi.newBuilder().add(KotlinJsonAdapterFactory()).build()