Slide 1

Slide 1 text

Upgrading to Moshi Eric Cochran Droidcon San Francisco November 5, 2017

Slide 2

Slide 2 text

Moshi?

Slide 3

Slide 3 text

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

Slide 4

Slide 4 text

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

Slide 5

Slide 5 text

Moshi? JSON serialization library for Java with a streaming and object-mapping API Gson 3 Lite and Kotlin

Slide 6

Slide 6 text

Why update from Gson? Inactive

Slide 7

Slide 7 text

Why update from Gson? Inactive Too lenient

Slide 8

Slide 8 text

Why update from Gson? Inactive Too lenient Large API

Slide 9

Slide 9 text

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

Slide 10

Slide 10 text

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

Slide 11

Slide 11 text

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…

Slide 12

Slide 12 text

Moshi optimizations Share buffer segments with other Okio users

Slide 13

Slide 13 text

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

Slide 17

Slide 17 text

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

Slide 18

Slide 18 text

Reflective field renaming @SerializedName("the_name") @Json(name = "the_name") Prefer using the domain’s naming convention

Slide 19

Slide 19 text

Streaming API com.google.gson.stream.JsonReader com.google.gson.stream.JsonWriter

Slide 20

Slide 20 text

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

Slide 21

Slide 21 text

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

Slide 22

Slide 22 text

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

Slide 23

Slide 23 text

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

Slide 24

Slide 24 text

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

Slide 25

Slide 25 text

Object Mapping TypeAdapter

Slide 26

Slide 26 text

Object Mapping TypeAdapter JsonAdapter

Slide 27

Slide 27 text

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

Slide 28

Slide 28 text

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

Slide 29

Slide 29 text

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

Slide 30

Slide 30 text

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/

Slide 31

Slide 31 text

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)

Slide 32

Slide 32 text

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

Slide 33

Slide 33 text

Object Mapping JsonSerializer JsonDeserializer

Slide 34

Slide 34 text

Object Mapping JsonSerializer JsonDeserializer These don’t stream!

Slide 35

Slide 35 text

Object Mapping TypeAdapter TypeAdapterFactory

Slide 36

Slide 36 text

Object Mapping TypeAdapter TypeAdapterFactory JsonAdapter JsonAdapter.Factory

Slide 37

Slide 37 text

Object Mapping TypeToken

Slide 38

Slide 38 text

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

Slide 39

Slide 39 text

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

Slide 40

Slide 40 text

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)

Slide 41

Slide 41 text

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

Slide 42

Slide 42 text

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

Slide 43

Slide 43 text

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?

Slide 44

Slide 44 text

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

Slide 45

Slide 45 text

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

Slide 46

Slide 46 text

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

Slide 47

Slide 47 text

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

Slide 48

Slide 48 text

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 49

Slide 49 text

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 50

Slide 50 text

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 51

Slide 51 text

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

Slide 52

Slide 52 text

Even Easier JsonAdapters @ToJson void toJson(JsonWriter writer, Foo value) @ToJson void toJson(JsonWriter writer, JsonAdapter delegate, ) @ToJson void toJson(JsonWriter writer, Foo value, JsonAdapter delegate, ) // Bar is a type that can already be serialized. @FromJson Bar fromJson(Foo value)

Slide 53

Slide 53 text

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

Slide 54

Slide 54 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 55

Slide 55 text

Polymorphic Types JsonElement (JsonObject, JsonArray, String, primitives)

Slide 56

Slide 56 text

Polymorphic Types JsonElement (JsonObject, JsonArray, String, primitives) Object (Map, List, String, primitives)

Slide 57

Slide 57 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 58

Slide 58 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 59

Slide 59 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 60

Slide 60 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 61

Slide 61 text

Polymorphic Types RuntimeTypeAdapterFactory

Slide 62

Slide 62 text

Polymorphic Types RuntimeTypeAdapterFactory ???

Slide 63

Slide 63 text

Updating Piecemeal

Slide 64

Slide 64 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 65

Slide 65 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 66

Slide 66 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 67

Slide 67 text

Kotlin

Slide 68

Slide 68 text

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

Slide 69

Slide 69 text

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

Slide 70

Slide 70 text

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

Slide 71

Slide 71 text

KotlinJsonAdapter data class TrainStation(val name: String, val cost: Int = 250, val trains: List) { "name": "Reading", "cost": "1000", "trains": [ {…}, null ] } val train = station.trains[1]

Slide 72

Slide 72 text

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

Slide 73

Slide 73 text

KotlinJsonAdapter val moshi = Moshi.Builder() // Add any other JsonAdapter factories. .add(KotlinJsonAdapterFactory()) .build() fun withKotlinJsonAdapterFactory(moshi: Moshi) = moshi.newBuilder().add(KotlinJsonAdapterFactory()).build()

Slide 74

Slide 74 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 75

Slide 75 text

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