Slide 1

Slide 1 text

Upgrading to Moshi Eric Cochran Droidcon Berlin September 5, 2017

Slide 2

Slide 2 text

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

Slide 3

Slide 3 text

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

Slide 4

Slide 4 text

What is Moshi? JSON serialization library for Java with a streaming and object-mapping API. Gson 2 => “Gson Lite”

Slide 5

Slide 5 text

Why update from Gson?

Slide 6

Slide 6 text

Why update from Gson? Gson: - Inactive

Slide 7

Slide 7 text

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

Slide 8

Slide 8 text

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

Slide 9

Slide 9 text

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

Slide 10

Slide 10 text

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

Slide 11

Slide 11 text

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…)

Slide 12

Slide 12 text

Moshi optimizations Share buffer segments with other Okio users.

Slide 13

Slide 13 text

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

Slide 14

Slide 14 text

How to Upgrade

Slide 15

Slide 15 text

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

Slide 16

Slide 16 text

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

Slide 17

Slide 17 text

Reflective field naming policy @SerializedName(“the_name”) => @Json(“the_name”) Prefer using the domain’s naming convention.

Slide 18

Slide 18 text

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

Slide 19

Slide 19 text

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

Slide 20

Slide 20 text

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.

Slide 21

Slide 21 text

setFailOnUnknown JsonReader.setFailOnUnkown(true) Fail when JsonReader.skipValue() is called to ensure you are not missing any JSON data while debugging.

Slide 22

Slide 22 text

Object Mapping TypeAdapter => JsonAdapter

Slide 23

Slide 23 text

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

Slide 24

Slide 24 text

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

Slide 25

Slide 25 text

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/

Slide 26

Slide 26 text

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)

Slide 27

Slide 27 text

Object Mapping JsonAdapter wrappers: serializeNulls(), nullSafe(), lenient(), indent(String), failOnUnknown()

Slide 28

Slide 28 text

Object Mapping No JsonSerializer, JsonDeserializer! These did not stream. TypeAdapter, TypeAdapterFactory => JsonAdapter, JsonAdapter.Factory

Slide 29

Slide 29 text

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)

Slide 30

Slide 30 text

Object Mapping: Unknown Enums enum Exercise { RUN, JUMP, WALK } Gson: exerciseTypeAdapter.fromJson(“jog”) == null Moshi: exerciseJsonAdapter.fromJson(“jog”) => throws JsonDataException

Slide 31

Slide 31 text

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?

Slide 32

Slide 32 text

Object Mapping: JsonQualifier Special-case type qualifiers: class Data { @JsonAdapter(WrappedStringTypeAdapter.class) String string; } => @Retention(RUNTIME) @JsonQualifier @interface WrappedString {} class Data { @WrappedString String string }

Slide 33

Slide 33 text

Object Mapping: JsonQualifier class WrappedStringTypeAdapter extends TypeAdapter { String read(JsonReader reader) throws IOException { reader.beginObject(); String string = reader.nextString(); reader.endObject(); return string; } }

Slide 34

Slide 34 text

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

Slide 35

Slide 35 text

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 fromJson(JsonReader reader) {...} void toJson(JsonWriter writer) {...} } } }

Slide 36

Slide 36 text

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 fromJson(JsonReader reader) {...} void toJson(JsonWriter writer) {...} } } }

Slide 37

Slide 37 text

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 fromJson(JsonReader reader) {...} void toJson(JsonWriter writer, Point value) {...} } } }

Slide 38

Slide 38 text

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 fromJson(JsonReader reader) {...} void toJson(JsonWriter writer, Point value) {...} } } } => class PointJsonAdapter { @FromJson Point fromJson(JsonReader reader) {...} @ToJson void toJson(JsonWriter writer, Point value) {...} }

Slide 39

Slide 39 text

Even Easier JsonAdapters @FromJson Foo fromJson(JsonReader reader) @FromJson Foo fromJson(JsonReader jsonReader, JsonAdapter delegate, ) @FromJson Foo fromJson(Bar value) // Bar is already a type that can be deserialized.

Slide 40

Slide 40 text

Even Easier JsonAdapters @FromJson Foo fromJson(JsonReader reader) @FromJson Foo fromJson(JsonReader jsonReader, JsonAdapter delegate, ) @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 delegate, ) @ToJson void toJson(JsonWriter writer, T value, JsonAdapter delegate, ) @ToJson Bar toJson(Foo value) // Bar is already a type that can be serialized.

Slide 41

Slide 41 text

Advanced: Polymorphic types class Animal { String type; } List animals = animalAdapter.fromJson(source)

Slide 42

Slide 42 text

Polymorphic types class Animal { String type; } List animals = animalAdapter.fromJson(source) class Dog extends Animal { String bark; } class Cat extends Animal { int meals; }

Slide 43

Slide 43 text

Polymorphic types Gson: JsonElement (JsonObject, JsonArray, String, primitives) Moshi: Object (Map, List, String, primitives)

Slide 44

Slide 44 text

Polymorphic types Gson: class AnimalTypeAdapter extends TypeAdapter { TypeAdapter elementAdapter; TypeAdapter catAdapter; TypeAdapter 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); // ... } }

Slide 45

Slide 45 text

Polymorphic types Gson: class AnimalTypeAdapter extends TypeAdapter { TypeAdapter elementAdapter; TypeAdapter catAdapter; TypeAdapter 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); // ... } }

Slide 46

Slide 46 text

Polymorphic types Moshi: class AnimalJsonAdapter extends JsonAdapter { JsonAdapter catAdapter; JsonAdapter dogAdapter; Animal fromJson(JsonReader in) { Map value = (Map) in.readJsonValue(); // Inspect the value. Decide what JsonAdapter to delegate to. if (value.get(“type”).equals(“cat”)) return catAdapter.fromJsonValue(value); // ... } }

Slide 47

Slide 47 text

Polymorphic types Moshi: class AnimalJsonAdapter extends JsonAdapter { JsonAdapter catAdapter; JsonAdapter dogAdapter; Animal fromJson(JsonReader in) { Map value = (Map) in.readJsonValue(); // Inspect the value. Decide what JsonAdapter to delegate to. if (value.get(“type”).equals(“cat”)) return catAdapter.fromJsonValue(value); // ... } }

Slide 48

Slide 48 text

Polymorphic types Gson: RuntimeTypeAdapterFactory Moshi: ???

Slide 49

Slide 49 text

Updating Piecemeal

Slide 50

Slide 50 text

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 newEndpoint(); @GET("/old_endpoint") @Gson Call oldEndpoint(); @GET("/old_endpoint") Call oldEndpointDefault(); // Will use fallback. }

Slide 51

Slide 51 text

Auto-Value-Moshi https://github.com/rharter/auto-value-moshi @AutoValue abstract class Data { abstract String string(); static JsonAdapter jsonAdapter(Moshi moshi) { return new AutoValue_Data.MoshiJsonAdapter(moshi); } }

Slide 52

Slide 52 text

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(); } }

Slide 53

Slide 53 text

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

Slide 54

Slide 54 text

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