Share
Embed
Say "hi" to Moshi (Android Budapest meetup - January 2020)
JSON serialization is an everyday task for Android developers, but it's still not a trivial one. Long used solutions such as Gson have serious pain points, especially when using them with Kotlin. Could Moshi be our saviour when it comes to dealing with JSON on Android? We'll look at how to use it, what problems it solves, and how nicely it plays with our favourite JVM language.
Talk recording: https://www.youtube.com/watch?v=qadYVYc-8mc
Márton Braun
January 30, 2020
Transcript
155 slides
-
Gson 101 val burger: Burger = Gson().fromJson("{...}", Burger::class.java) val json:
String = Gson().toJson(Burger()) @zsmb13 -
Gson 101 val burger: Burger = Gson().fromJson("{...}", Burger::class.java) val json:
String = Gson().toJson(Burger()) @zsmb13 -
Gson 101 val burger: Burger = Gson().fromJson("{...}", Burger::class.java) val json:
String = Gson().toJson(Burger()) @zsmb13 -
Gson 101 val gson = Gson() val burger: Burger =
Gson().fromJson("{...}", Burger::class.java) val json: String = Gson().toJson(Burger()) @zsmb13 -
Gson 101 val gson = Gson() val burger: Burger =
gson.fromJson("{...}", Burger::class.java) val json: String = gson.toJson(Burger()) @zsmb13 -
Gson val gson = Gson() val burger: Burger = gson.fromJson("{...}",
Burger::class.java) val json: String = gson.toJson(Burger()) 101 @zsmb13 -
Gson val gson = Gson() gson.toJson(Date()) "Jan 30, 2020 7:03:50
PM" : built-in types @zsmb13 -
Gson val gson = Gson() gson.toJson(Date()) "Jan 30, 2020 7:03:50
PM" gson.toJson(Calendar.getInstance()) : built-in types @zsmb13 -
Gson val gson = Gson() gson.toJson(Date()) "Jan 30, 2020 7:03:50
PM" gson.toJson(Calendar.getInstance()) {"year":2020,"month":0,"dayOfMonth":30,"hourOfDay":19, "minute":03,"second":50} : built-in types @zsmb13 -
Gson val gson = Gson() gson.toJson(Date()) "Jan 30, 2020 7:03:50
PM" gson.toJson(Calendar.getInstance()) {"year":2020,"month":0,"dayOfMonth":30,"hourOfDay":19, "minute":03,"second":50} gson.toJson(Random()) : built-in types @zsmb13 -
Gson val gson = Gson() gson.toJson(Date()) "Jan 30, 2020 7:03:50
PM" gson.toJson(Calendar.getInstance()) {"year":2020,"month":0,"dayOfMonth":30,"hourOfDay":19, "minute":03,"second":50} gson.toJson(Random()) {"seed":{"value":137564286790437},"nextNextGaussian":0.0, "haveNextNextGaussian":false} : built-in types @zsmb13 -
val gson = GsonBuilder() .setFieldNamingPolicy( FieldNamingPolicy.LOWER_CASE_WITH_UNDERSCORES) .create() Gson: field naming
data class Burger( val name: String, val totalCost: Int, val salesPrice: Int ) @zsmb13 -
val gson = GsonBuilder() .setFieldNamingPolicy( FieldNamingPolicy.LOWER_CASE_WITH_UNDERSCORES) .create() Gson: field naming
data class Burger( val name: String, val totalCost: Int, val salesPrice: Int ) gson.toJson(Burger("Classic", 10, 13)) {"name":"Classic","total_cost":10,"sales_price":13} @zsmb13 -
@SerializedName("name") @SerializedName("total_cost") @SerializedName("sales_price") Gson() Gson: field naming gson.toJson(Burger("Classic", 10, 13))
{"name":"Classic","total_cost":10,"sales_price":13} data class Burger( val gson = val name: String, val totalCost: Int, ) val salesPrice: Int @zsmb13 -
data class Burger( val name: String, val price: Int )
Gson: missing data Gson() val gson = @zsmb13 -
data class Burger( val name: String, val price: Int )
Gson: missing data val burger = gson.fromJson("{\"name\": \"Cheeseburger\"}", Burger::class.java) Gson() val gson = @zsmb13 -
data class Burger( val name: String, val price: Int )
Gson: missing data val burger = gson.fromJson("{\"name\": \"Cheeseburger\"}", Burger::class.java) Burger(name=Cheeseburger, price=0) Gson() val gson = @zsmb13 -
data class Burger( val name: String, val description: String )
val gson = Gson() Gson: missing data val burger = gson.fromJson("{\"name\": \"Cheeseburger\"}", Burger::class.java) @zsmb13 -
data class Burger( val name: String, val description: String )
val gson = Gson() Gson: missing data val burger = gson.fromJson("{\"name\": \"Cheeseburger\"}", Burger::class.java) Burger(name=Cheeseburger, description=null) @zsmb13 -
data class Burger( val name: String, val description: String =
"One of our best burgers!" ) val gson = Gson() Gson: missing data val burger = gson.fromJson("{\"name\": \"Cheeseburger\"}", Burger::class.java) @zsmb13 -
data class Burger( val name: String, val description: String =
"One of our best burgers!" ) val gson = Gson() Gson: missing data val burger = gson.fromJson("{\"name\": \"Cheeseburger\"}", Burger::class.java) Burger(name=Cheeseburger, description=null) @zsmb13 -
val gson = Gson() Gson: malformed data data class Burger(val
name: String, val price: Int) data class Restaurant(val menu: List<Burger>) @zsmb13 -
val gson = Gson() Gson: malformed data data class Burger(val
name: String, val price: Int) data class Restaurant(val menu: List<Burger>) val json = """{ "menu": [ { "name": "Classic", "price": 10 }, { "name": "Cheeseburger", "price": 13 }, { "name": "Double", "price": "crocodile" } ] }""" @zsmb13 -
val gson = Gson() Gson: malformed data data class Burger(val
name: String, val price: Int) data class Restaurant(val menu: List<Burger>) val json = """{ "menu": [ { "name": "Classic", "price": 10 }, { "name": "Cheeseburger", "price": 13 }, { "name": "Double", "price": "crocodile" } ] }""" @zsmb13 -
val gson = Gson() Gson: malformed data data class Burger(val
name: String, val price: Int) data class Restaurant(val menu: List<Burger>) val json = """{ "menu": [ { "name": "Classic", "price": 10 }, { "name": "Cheeseburger", "price": 13 }, { "name": "Double", "price": "crocodile" } ] }""" val restaurant = gson.fromJson(json, Restaurant::class.java) @zsmb13 -
Gson: malformed data val gson = Gson() data class Burger(val
name: String, val price: Int) data class Restaurant(val menu: List<Burger>) val json = """{ "menu": [ { "name": "Classic", "price": 10 }, { "name": "Cheeseburger", "price": 13 }, { "name": "Double", "price": "crocodile" } ] }""" val restaurant = gson.fromJson(json, Restaurant::class.java) Exception in thread "main" com.google.gson.JsonSyntaxException: java.lang.NumberFormatException: For input string: "crocodile" @zsmb13 -
val gson = Gson() Gson: malformed data data class Burger(val
name: String, val price: Int) data class Restaurant(val menu: List<Burger>) val json = """{ "menu": [ { "name": "Classic", "price": 10 }, { "name": "Cheeseburger", "price": 13 }, { "name": "Double", "price": "" } ] }""" val restaurant = gson.fromJson(json, Restaurant::class.java) Exception in thread "main" com.google.gson.JsonSyntaxException: java.lang.NumberFormatException: For input string: "" @zsmb13 -
public JavaBurger(String name, int price) { this.name = name; this.price
= price; } public String getName() { return name; } public void setName(String name) { this.name = name; } public int getPrice() { return price; } public void setPrice(int price) { this.price = price; } Moshi 101 } public class JavaBurger { private String name; private int price; @zsmb13 -
// ... public JavaBurger(String name, int price) { this.name =
name; this.price = price; } public String getName() { return name; } public void setName(String name) { this.name = name; } public int getPrice() { return price; } public void setPrice(int price) { this.price = price; } Moshi 101 } public class JavaBurger { private String name; private int price; @zsmb13 -
// ... public JavaBurger(String name, int price) { this.name =
name; this.price = price; } public String getName() { return name; } public void setName(String name) { this.name = name; } public int getPrice() { return price; } public void setPrice(int price) { this.price = price; } Moshi 101 val moshi = Moshi.Builder().build() } public class JavaBurger { private String name; private int price; @zsmb13 -
// ... public JavaBurger(String name, int price) { this.name =
name; this.price = price; } public String getName() { return name; } public void setName(String name) { this.name = name; } public int getPrice() { return price; } public void setPrice(int price) { this.price = price; } Moshi 101 val moshi = Moshi.Builder().build() val json = moshi.toJson(JavaBurger("Classic", 10)) } public class JavaBurger { private String name; private int price; @zsmb13 -
// ... Moshi 101 val moshi = Moshi.Builder().build() val json
= moshi.toJson(JavaBurger("Classic", 10)) } public class JavaBurger { private String name; private int price; @zsmb13 -
// ... Moshi 101 val moshi = Moshi.Builder().build() val json
= moshi.toJson(JavaBurger("Classic", 10)) } public class JavaBurger { private String name; private int price; val burgerAdapter: JsonAdapter<JavaBurger> = moshi.adapter(JavaBurger::class.java) @zsmb13 -
// ... Moshi 101 val moshi = Moshi.Builder().build() } public
class JavaBurger { private String name; private int price; val burgerAdapter: JsonAdapter<JavaBurger> = moshi.adapter(JavaBurger::class.java) val json = burgerAdapter.toJson(JavaBurger("Classic", 10)) @zsmb13 -
// ... Moshi 101 val moshi = Moshi.Builder().build() } public
class JavaBurger { private String name; private int price; val burgerAdapter: JsonAdapter<JavaBurger> = moshi.adapter(JavaBurger::class.java) val json = burgerAdapter.toJson(JavaBurger("Classic", 10)) {"name":"Classic","price":10} @zsmb13 -
// ... Moshi 101 val moshi = Moshi.Builder().build() } public
class JavaBurger { private String name; private int price; val burgerAdapter: JsonAdapter<JavaBurger> = moshi.adapter(JavaBurger::class.java) val json = burgerAdapter.toJson(JavaBurger("Classic", 10)) {"name":"Classic","price":10} val burger = burgerAdapter.fromJson(json) @zsmb13 -
Burger::class.java) val moshi = Moshi.Builder().build() (val : String, val :
Int) val burgerAdapter: JsonAdapter<Burger> = moshi.adapter( Burger class name price @zsmb13 Moshi ft. Kotlin -
Burger::class.java) val moshi = Moshi.Builder().build() (val : String, val :
Int) val burgerAdapter: JsonAdapter<Burger> = moshi.adapter( Burger class name price @zsmb13 Moshi ft. Kotlin Exception in thread "main" java.lang.IllegalArgumentException: Cannot serialize Kotlin type com.example.Burger. Reflective serialization of Kotlin classes without using kotlin-reflect has undefined and unexpected behavior. Please use KotlinJsonAdapter from the moshi-kotlin artifact or use code gen from the moshi-kotlin- codegen artifact. -
Moshi ft. Kotlin val moshi = Moshi.Builder().build() val burgerAdapter: JsonAdapter<Burger>
= moshi.adapter(Burger::class.java) class Burger(val name: String, val price: Int) @zsmb13 Exception in thread "main" java.lang.IllegalArgumentException: Cannot serialize Kotlin type com.example.Burger. Reflective serialization of Kotlin classes without using kotlin-reflect has undefined and unexpected behavior. Please use KotlinJsonAdapter from the moshi-kotlin artifact or use code gen from the moshi-kotlin- codegen artifact. -
Moshi ft. Kotlin val moshi = Moshi.Builder().build() val burgerAdapter: JsonAdapter<Burger>
= moshi.adapter(Burger::class.java) class Burger(val name: String, val price: Int) @zsmb13 Exception in thread "main" java.lang.IllegalArgumentException: Cannot serialize Kotlin type com.example.Burger. Reflective serialization of Kotlin classes without using kotlin-reflect has undefined and unexpected behavior. Please use KotlinJsonAdapter from the moshi-kotlin artifact or use code gen from the moshi-kotlin- codegen artifact. -
val moshi = Moshi.Builder().build() val burgerAdapter: JsonAdapter<Burger> = moshi.adapter(Burger::class.java) class
Burger(val name: String, val price: Int) @zsmb13 Moshi ft. Kotlin Exception in thread "main" java.lang.IllegalArgumentException: Cannot serialize Kotlin type com.example.Burger. Reflective serialization of Kotlin classes without using kotlin-reflect has undefined and unexpected behavior. Please use KotlinJsonAdapter from the moshi-kotlin artifact or use code gen from the moshi-kotlin- codegen artifact. -
val moshi = Moshi.Builder().build() val burgerAdapter: JsonAdapter<Burger> = moshi.adapter(Burger::class.java) class
Burger(val name: String, val price: Int) def moshi_version = '1.9.2' implementation "com.squareup.moshi:moshi:$moshi_version" @zsmb13 Moshi: reflection -
val moshi = Moshi.Builder().build() Moshi: reflection val burgerAdapter: JsonAdapter<Burger> =
moshi.adapter(Burger::class.java) class Burger(val name: String, val price: Int) def moshi_version = '1.9.2' implementation "com.squareup.moshi:moshi:$moshi_version" implementation "com.squareup.moshi:moshi-kotlin:$moshi_version" @zsmb13 -
val moshi = Moshi.Builder() Moshi: reflection val burgerAdapter: JsonAdapter<Burger> =
moshi.adapter(Burger::class.java) class Burger(val name: String, val price: Int) .build() @zsmb13 -
Moshi: reflection .add(KotlinJsonAdapterFactory()) val burgerAdapter: JsonAdapter<Burger> = moshi.adapter(Burger::class.java) class Burger(val
name: String, val price: Int) .build() val moshi = Moshi.Builder() @zsmb13 -
Moshi: reflection .add(KotlinJsonAdapterFactory()) val burgerAdapter: JsonAdapter<Burger> = moshi.adapter(Burger::class.java) class Burger(val
name: String, val price: Int) val json = burgerAdapter.toJson(Burger("Classic", 10)) val burger = burgerAdapter.fromJson(json) .build() val moshi = Moshi.Builder() @zsmb13 -
Moshi: reflection def moshi_version = '1.9.2' implementation "com.squareup.moshi:moshi:$moshi_version" implementation "com.squareup.moshi:moshi-kotlin:$moshi_version"
@zsmb13 Exception in thread "main" java.lang.IllegalArgumentException: Cannot serialize Kotlin type com.example.Burger. Reflective serialization of Kotlin classes without using kotlin-reflect has undefined and unexpected behavior. Please use KotlinJsonAdapter from the moshi-kotlin artifact or use code gen from the moshi-kotlin- codegen artifact. -
implementation moshi-kotlin:$moshi_version" "com.squareup.moshi: def moshi_version = '1.9.2' implementation "com.squareup.moshi:moshi:$moshi_version" @zsmb13
Moshi: reflection Exception in thread "main" java.lang.IllegalArgumentException: Cannot serialize Kotlin type com.example.Burger. Reflective serialization of Kotlin classes without using kotlin-reflect has undefined and unexpected behavior. Please use KotlinJsonAdapter from the moshi-kotlin artifact or use code gen from the artifact. moshi-kotlin- codegen -
dependencies { } Moshi: codegen apply plugin: 'kotlin' apply plugin:
'kotlin-kapt' def moshi_version = '1.9.2' implementation "com.squareup.moshi:moshi:$moshi_version" kapt "com.squareup.moshi:moshi-kotlin-codegen:$moshi_version" @zsmb13 -
@JsonClass(generateAdapter = true) class Burger(val name: String, val price: Int)
@zsmb13 Moshi: codegen -
@JsonClass(generateAdapter = true) class Burger(val name: String, val price: Int)
val moshi = Moshi.Builder().build() val burgerAdapter = moshi.adapter(Burger::class.java) val json = burgerAdapter.toJson(Burger("Double", 18))) @zsmb13 Moshi: codegen -
dependencies { def moshi_version = '1.9.2' implementation "com.squareup.moshi:moshi:$moshi_version" implementation "com.squareup.moshi:moshi-kotlin:$moshi_version"
kapt "com.squareup.moshi:moshi-kotlin-codegen:$moshi_version" } apply plugin: 'kotlin' apply plugin: 'kotlin-kapt' val moshi = Moshi.Builder() .add(KotlinJsonAdapterFactory()) .build() @zsmb13 Moshi: mixed use -
Moshi: mixed use dependencies { def moshi_version = '1.9.2' implementation
"com.squareup.moshi:moshi:$moshi_version" implementation "com.squareup.moshi:moshi-kotlin:$moshi_version" kapt "com.squareup.moshi:moshi-kotlin-codegen:$moshi_version" } apply plugin: 'kotlin' apply plugin: 'kotlin-kapt' val moshi = Moshi.Builder() .add(KotlinJsonAdapterFactory()) .build() @zsmb13 -
dependencies { def moshi_version = '1.9.2' implementation "com.squareup.moshi:moshi:$moshi_version" implementation "com.squareup.moshi:moshi-kotlin:$moshi_version"
kapt "com.squareup.moshi:moshi-kotlin-codegen:$moshi_version" } apply plugin: 'kotlin' apply plugin: 'kotlin-kapt' val moshi = Moshi.Builder() .add(KotlinJsonAdapterFactory()) .build() @zsmb13 Moshi: mixed use -
class CustomBurgerAdapter { TODO("Implement deserialization") @ToJson fun toJson(writer: JsonWriter, value:
Burger?) { TODO("Implement serialization") } } @FromJson fun fromJson(reader: JsonReader): Burger? { } @zsmb13 Moshi: custom adapters -
Moshi: custom adapters @FromJson fun fromJson(reader: JsonReader): Burger? { var
name: String? = null var price: Int? = null } @zsmb13 -
Moshi: custom adapters @FromJson fun fromJson(reader: JsonReader): Burger? { var
name: String? = null var price: Int? = null reader.beginObject() reader.endObject() } @zsmb13 -
Moshi: custom adapters @FromJson fun fromJson(reader: JsonReader): Burger? { var
name: String? = null var price: Int? = null reader.beginObject() while (reader.hasNext()) { } reader.endObject() } @zsmb13 -
Moshi: custom adapters @FromJson fun fromJson(reader: JsonReader): Burger? { var
name: String? = null var price: Int? = null reader.beginObject() while (reader.hasNext()) { when (reader.nextName()) { "name" -> name = reader.nextString() "price" -> price = reader.nextInt() else -> reader.skipValue() } } reader.endObject() } @zsmb13 -
@FromJson fun fromJson(reader: JsonReader): Burger? { var name: String? =
null var price: Int? = null reader.beginObject() while (reader.hasNext()) { when (reader.nextName()) { "name" -> name = reader.nextString() "price" -> price = reader.nextInt() else -> reader.skipValue() } } reader.endObject() return Burger(name!!, price!!) } @zsmb13 : custom adapters Moshi -
class BurgerJsonAdapter( moshi: Moshi ) : JsonAdapter<Burger>() { override fun
toString(): String = buildString(28) { append("GeneratedJsonAdapter(").append("Burger").append(')') } private val options: JsonReader.Options = JsonReader.Options.of("name", "price") private val stringAdapter: JsonAdapter<String> = moshi.adapter(String::class.java, emptySet(), "name") private val intAdapter: JsonAdapter<Int> = moshi.adapter(Int::class.java, emptySet(), "price") override fun fromJson(reader: JsonReader): Burger { var name: String? = null var price: Int? = null reader.beginObject() while (reader.hasNext()) { when (reader.selectName(options)) { 0 -> name = stringAdapter.fromJson(reader) ?: throw Util.unexpectedNull("name", "name", reader) 1 -> price = intAdapter.fromJson(reader) ?: throw Util.unexpectedNull("price", "price", reader) -1 -> { reader.skipName() reader.skipValue() } } } reader.endObject() return Burger( name = name ?: throw Util.missingProperty("name", "name", reader), price = price ?: throw Util.missingProperty("price", "price", reader) ) } override fun toJson(writer: JsonWriter, value: Burger?) { if (value == null) { throw NullPointerException( "value was null! Wrap in .nullSafe() to write nullable values.") } writer.beginObject() writer.name("name") stringAdapter.toJson(writer, value.name) writer.name("price") intAdapter.toJson(writer, value.price) writer.endObject() } } @zsmb13 Moshi’s generated adapter -
class BurgerJsonAdapter( moshi: Moshi ) : JsonAdapter<Burger>() { override fun
toString(): String = buildString(28) { append("GeneratedJsonAdapter(").append("Burger").append(')') } private val options: JsonReader.Options = JsonReader.Options.of("name", "price") private val stringAdapter: JsonAdapter<String> = moshi.adapter(String::class.java, emptySet(), "name") private val intAdapter: JsonAdapter<Int> = moshi.adapter(Int::class.java, emptySet(), "price") Moshi’s generated adapter @zsmb13 -
private val intAdapter: JsonAdapter<Int> = moshi.adapter(Int::class.java, emptySet(), "price") override fun
fromJson(reader: JsonReader): Burger { var name: String? = null var price: Int? = null reader.beginObject() while (reader.hasNext()) { when (reader.selectName(options)) { 0 -> name = stringAdapter.fromJson(reader) ?: throw Util.unexpectedNull("name", "name", reader) 1 -> price = intAdapter.fromJson(reader) ?: throw Util.unexpectedNull("price", "price", reader) -1 -> { reader.skipName() reader.skipValue() } } } reader.endObject() Moshi’s generated adapter @zsmb13 -
private val intAdapter: JsonAdapter<Int> = moshi.adapter(Int::class.java, emptySet(), "price") override fun
fromJson(reader: JsonReader): Burger { var name: String? = null var price: Int? = null reader.beginObject() while (reader.hasNext()) { when (reader.selectName(options)) { 0 -> name = stringAdapter.fromJson(reader) ?: throw Util.unexpectedNull("name", "name", reader) 1 -> price = intAdapter.fromJson(reader) ?: throw Util.unexpectedNull("price", "price", reader) -1 -> { reader.skipName() reader.skipValue() } } } reader.endObject() Moshi’s generated adapter @zsmb13 -
private val intAdapter: JsonAdapter<Int> = moshi.adapter(Int::class.java, emptySet(), "price") override fun
fromJson(reader: JsonReader): Burger { var name: String? = null var price: Int? = null reader.beginObject() while (reader.hasNext()) { when (reader.selectName(options)) { 0 -> name = stringAdapter.fromJson(reader) ?: throw Util.unexpectedNull("name", "name", reader) 1 -> price = intAdapter.fromJson(reader) ?: throw Util.unexpectedNull("price", "price", reader) -1 -> { reader.skipName() reader.skipValue() } } } reader.endObject() Moshi’s generated adapter @zsmb13 -
private val intAdapter: JsonAdapter<Int> = moshi.adapter(Int::class.java, emptySet(), "price") override fun
fromJson(reader: JsonReader): Burger { var name: String? = null var price: Int? = null reader.beginObject() while (reader.hasNext()) { when (reader.selectName(options)) { 0 -> name = stringAdapter.fromJson(reader) ?: throw Util.unexpectedNull("name", "name", reader) 1 -> price = intAdapter.fromJson(reader) ?: throw Util.unexpectedNull("price", "price", reader) -1 -> { reader.skipName() reader.skipValue() } } } reader.endObject() Moshi’s generated adapter @zsmb13 -
private val intAdapter: JsonAdapter<Int> = moshi.adapter(Int::class.java, emptySet(), "price") override fun
fromJson(reader: JsonReader): Burger { var name: String? = null var price: Int? = null reader.beginObject() while (reader.hasNext()) { when (reader.selectName(options)) { 0 -> name = stringAdapter.fromJson(reader) ?: throw Util.unexpectedNull("name", "name", reader) 1 -> price = intAdapter.fromJson(reader) ?: throw Util.unexpectedNull("price", "price", reader) -1 -> { reader.skipName() reader.skipValue() } } } reader.endObject() Moshi’s generated adapter @zsmb13 -
override fun toString(): String = buildString(28) { append("GeneratedJsonAdapter(").append("Burger").append(')') } private
val options: JsonReader.Options = JsonReader.Options.of("name", "price") private val stringAdapter: JsonAdapter<String> = moshi.adapter(String::class.java, emptySet(), "name") private val intAdapter: JsonAdapter<Int> = moshi.adapter(Int::class.java, emptySet(), "price") override fun fromJson(reader: JsonReader): Burger { var name: String? = null var price: Int? = null reader.beginObject() while (reader.hasNext()) { when (reader.selectName(options)) { 0 -> name = stringAdapter.fromJson(reader) ?: throw Util.unexpectedNull("name", "name", reader) Moshi’s generated adapter @zsmb13 -
private val intAdapter: JsonAdapter<Int> = moshi.adapter(Int::class.java, emptySet(), "price") override fun
fromJson(reader: JsonReader): Burger { var name: String? = null var price: Int? = null reader.beginObject() while (reader.hasNext()) { when (reader.selectName(options)) { 0 -> name = stringAdapter.fromJson(reader) ?: throw Util.unexpectedNull("name", "name", reader) 1 -> price = intAdapter.fromJson(reader) ?: throw Util.unexpectedNull("price", "price", reader) -1 -> { reader.skipName() reader.skipValue() } } } reader.endObject() Moshi’s generated adapter @zsmb13 -
private val options: JsonReader.Options = JsonReader.Options.of("name", "price") private val stringAdapter:
JsonAdapter<String> = moshi.adapter(String::class.java, emptySet(), "name") private val intAdapter: JsonAdapter<Int> = moshi.adapter(Int::class.java, emptySet(), "price") override fun fromJson(reader: JsonReader): Burger { var name: String? = null var price: Int? = null reader.beginObject() while (reader.hasNext()) { when (reader.selectName(options)) { 0 -> name = stringAdapter.fromJson(reader) ?: throw Util.unexpectedNull("name", "name", reader) 1 -> price = intAdapter.fromJson(reader) ?: throw Util.unexpectedNull("price", "price", reader) -1 -> { Moshi’s generated adapter @zsmb13 -
throw Util.unexpectedNull("price", "price", reader) -1 -> { reader.skipName() reader.skipValue() }
} } reader.endObject() return Burger( name = name ?: throw Util.missingProperty("name", "name", reader), price = price ?: throw Util.missingProperty("price", "price", reader) ) } } Moshi’s generated adapter @zsmb13 -
throw Util.unexpectedNull("price", "price", reader) -1 -> { reader.skipName() reader.skipValue() }
} } reader.endObject() return Burger( name = name ?: throw Util.missingProperty("name", "name", reader), price = price ?: throw Util.missingProperty("price", "price", reader) ) } } @zsmb13 Moshi’s generated adapter -
Moshi: built-in types val moshi = Moshi.Builder().build() val adapter: JsonAdapter<Date>
= moshi.adapter(Date::class.java) Exception in thread "main" java.lang.IllegalArgumentException: Platform class java.util.Date requires explicit JsonAdapter to be registered @zsmb13 -
Moshi: built-in types def moshi_version = '1.9.2' implementation "com.squareup.moshi:moshi:$moshi_version" kapt
"com.squareup.moshi:moshi-kotlin-codegen:$moshi_version" val moshi = Moshi.Builder().build() val adapter: JsonAdapter<Date> = moshi.adapter(Date::class.java) @zsmb13 -
Moshi: built-in types def moshi_version = '1.9.2' implementation "com.squareup.moshi:moshi:$moshi_version" kapt
"com.squareup.moshi:moshi-kotlin-codegen:$moshi_version" implementation "com.squareup.moshi:moshi-adapters:$moshi_version" val moshi = Moshi.Builder().build() val adapter: JsonAdapter<Date> = moshi.adapter(Date::class.java) @zsmb13 -
val adapter: JsonAdapter<Date> = moshi.adapter(Date::class.java) def moshi_version = '1.9.2' implementation
"com.squareup.moshi:moshi:$moshi_version" implementation "com.squareup.moshi:moshi-adapters:$moshi_version" kapt "com.squareup.moshi:moshi-kotlin-codegen:$moshi_version" .add(Date::class.java, Rfc3339DateJsonAdapter()) .build() val moshi = Moshi.Builder() @zsmb13 Moshi: built-in types -
val adapter: JsonAdapter<Date> = moshi.adapter(Date::class.java) def moshi_version = '1.9.2' implementation
"com.squareup.moshi:moshi:$moshi_version" implementation "com.squareup.moshi:moshi-adapters:$moshi_version" kapt "com.squareup.moshi:moshi-kotlin-codegen:$moshi_version" .add(Date::class.java, Rfc3339DateJsonAdapter()) 2020-01-30T19:03:50.250Z .build() val moshi = Moshi.Builder() @zsmb13 Moshi: built-in types -
@JsonClass(generateAdapter = true) data class Burger( @Json(name = "name") val
name: String, @Json(name = "total_cost") val totalCost: Int, @Json(name = "sales_price") val salesPrice: Int ) @zsmb13 Moshi: field naming -
@JsonClass(generateAdapter = true) data class Burger( @Json(name = "name") val
name: String, @Json(name = "total_cost") val totalCost: Int, @Json(name = "sales_price") val salesPrice: Int ) moshi.adapter(Burger::class.java) .toJson(Burger("Classic", 10, 13)) @zsmb13 Moshi: field naming -
@JsonClass(generateAdapter = true) data class Burger( @Json(name = "name") val
name: String, @Json(name = "total_cost") val totalCost: Int, @Json(name = "sales_price") val salesPrice: Int ) moshi.adapter(Burger::class.java) .toJson(Burger("Classic", 10, 13)) {"name":"Classic","total_cost":10,"sales_price":13} @zsmb13 Moshi: field naming -
@JsonClass(generateAdapter = true) data class Burger( val name: String, val
total_cost: Int, val sales_price: Int ) moshi.adapter(Burger::class.java) .toJson(Burger("Classic", 10, 13)) {"name":"Classic","total_cost":10,"sales_price":13} @zsmb13 Moshi: field naming -
@JsonClass(generateAdapter = true) data class Burger( val name: String, val
price: Int ) @zsmb13 Moshi: missing data -
@JsonClass(generateAdapter = true) data class Burger( val name: String, val
price: Int ) Moshi: missing data val moshi = Moshi.Builder().build() val burgerAdapter = moshi.adapter(Burger::class.java) val burger = burgerAdapter.fromJson( "{\"name\": \"Cheeseburger\"}" ) @zsmb13 -
@JsonClass(generateAdapter = true) data class Burger( val name: String, val
price: Int ) Moshi: missing data val moshi = Moshi.Builder().build() val burgerAdapter = moshi.adapter(Burger::class.java) val burger = burgerAdapter.fromJson( "{\"name\": \"Cheeseburger\"}" ) Exception in thread "main" com.squareup.moshi.JsonDataException: Required value 'price' missing at $ @zsmb13 -
@JsonClass(generateAdapter = true) data class Burger( val name: String, val
description: String ) Moshi: missing data val moshi = Moshi.Builder().build() val burgerAdapter = moshi.adapter(Burger::class.java) val burger = burgerAdapter.fromJson( "{\"name\": \"Cheeseburger\"}" ) @zsmb13 -
@JsonClass(generateAdapter = true) data class Burger( val name: String, val
description: String ) Moshi: missing data val moshi = Moshi.Builder().build() val burgerAdapter = moshi.adapter(Burger::class.java) val burger = burgerAdapter.fromJson( "{\"name\": \"Cheeseburger\"}" ) Exception in thread "main" com.squareup.moshi.JsonDataException: Required value 'description' missing at $ @zsmb13 -
@JsonClass(generateAdapter = true) data class Burger( val name: String, val
description: String? ) Moshi: missing data val moshi = Moshi.Builder().build() val burgerAdapter = moshi.adapter(Burger::class.java) val burger = burgerAdapter.fromJson( "{\"name\": \"Cheeseburger\"}" ) @zsmb13 -
@JsonClass(generateAdapter = true) data class Burger( val name: String, val
description: String? ) Moshi: missing data val moshi = Moshi.Builder().build() val burgerAdapter = moshi.adapter(Burger::class.java) val burger = burgerAdapter.fromJson( "{\"name\": \"Cheeseburger\"}" ) Burger(name=Cheeseburger, description=null) @zsmb13 -
Burger(name=Cheeseburger, description=null) @JsonClass(generateAdapter = true) data class Burger( val name:
String, val description: String = "One of our best burgers!" ) Moshi: missing data val moshi = Moshi.Builder().build() val burgerAdapter = moshi.adapter(Burger::class.java) val burger = burgerAdapter.fromJson( "{\"name\": \"Cheeseburger\"}" ) @zsmb13 -
Burger(name=Cheeseburger, description=null) @JsonClass(generateAdapter = true) data class Burger( val name:
String, val description: String = "One of our best burgers!" ) Moshi: missing data val moshi = Moshi.Builder().build() val burgerAdapter = moshi.adapter(Burger::class.java) val burger = burgerAdapter.fromJson( "{\"name\": \"Cheeseburger\"}" ) Burger(name=Cheeseburger, description=One of our best burgers!) @zsmb13 -
val moshi = Moshi.Builder() Moshi: customization options @JsonClass(generateAdapter = true)
data class Burger(val name: String, val price: Int) .build() val burgerAdapter = moshi.adapter(Burger::class.java) val burger = burgerAdapter.fromJson( "{\"name\": \"Cheeseburger\", \"price\": 13}" ) @zsmb13 -
Moshi: customization options @JsonClass(generateAdapter = true) data class Burger(val name:
String, val price: Int) .add(...) val burgerAdapter = moshi.adapter(Burger::class.java) val burger = burgerAdapter.fromJson( "{\"name\": \"Cheeseburger\", \"price\": 13}" ) .build() val moshi = Moshi.Builder() @zsmb13 -
val moshi = Moshi.Builder() Moshi: customization options @JsonClass(generateAdapter = true)
data class Burger(val name: String, val price: Int) .build() val burgerAdapter = moshi.adapter(Burger::class.java) val burger = burgerAdapter.fromJson( "{\"name\": \"Cheeseburger\", \"price\": 13}" ) @zsmb13 -
Moshi: customization options @JsonClass(generateAdapter = true) data class Burger(val name:
String, val price: Int) val moshi = Moshi.Builder().build() val burgerAdapter = moshi.adapter(Burger::class.java) JsonAdapter<Burger> @zsmb13 -
Moshi: customization options @JsonClass(generateAdapter = true) data class Burger(val name:
String, val price: Int) val moshi = Moshi.Builder().build() val burgerAdapter = moshi.adapter(Burger::class.java) @zsmb13 -
Moshi: customization options @JsonClass(generateAdapter = true) data class Burger(val name:
String, val price: Int?) val moshi = Moshi.Builder().build() val burgerAdapter = moshi.adapter(Burger::class.java) burgerAdapter.toJson(Burger("Cancelled", null)) @zsmb13 -
Moshi: customization options @JsonClass(generateAdapter = true) data class Burger(val name:
String, val price: Int?) val moshi = Moshi.Builder().build() val burgerAdapter = moshi.adapter(Burger::class.java) burgerAdapter.toJson(Burger("Cancelled", null)) {"name":"Cancelled"} @zsmb13 -
Moshi: customization options @JsonClass(generateAdapter = true) data class Burger(val name:
String, val price: Int?) val moshi = Moshi.Builder().build() val burgerAdapter = moshi.adapter(Burger::class.java) .serializeNulls() burgerAdapter.toJson(Burger("Cancelled", null)) {"name":"Cancelled","price":null} @zsmb13 -
Moshi: customization options @JsonClass(generateAdapter = true) data class Burger(val name:
String, val price: Int) val moshi = Moshi.Builder().build() val burgerAdapter = moshi.adapter(Burger::class.java) burgerAdapter.toJson(null) @zsmb13 -
Moshi: customization options @JsonClass(generateAdapter = true) data class Burger(val name:
String, val price: Int) val moshi = Moshi.Builder().build() val burgerAdapter = moshi.adapter(Burger::class.java) burgerAdapter.toJson(null) null @zsmb13 -
Moshi: customization options @JsonClass(generateAdapter = true) data class Burger(val name:
String, val price: Int) val moshi = Moshi.Builder().build() val burgerAdapter = moshi.adapter(Burger::class.java) .nullSafe() burgerAdapter.toJson(null) null @zsmb13 -
Moshi: customization options @JsonClass(generateAdapter = true) data class Burger(val name:
String, val price: Int) val moshi = Moshi.Builder().build() val burgerAdapter = moshi.adapter(Burger::class.java) .nonNull() burgerAdapter.toJson(null) @zsmb13 -
Moshi: customization options @JsonClass(generateAdapter = true) data class Burger(val name:
String, val price: Int) val moshi = Moshi.Builder().build() val burgerAdapter = moshi.adapter(Burger::class.java) .nonNull() burgerAdapter.toJson(null) Exception in thread "main" com.squareup.moshi.JsonDataException: Unexpected null at $ @zsmb13 -
Moshi: customization options @JsonClass(generateAdapter = true) data class Burger(val name:
String, val price: Int) val moshi = Moshi.Builder().build() val burgerAdapter = moshi.adapter(Burger::class.java) @zsmb13 -
Moshi: customization options @JsonClass(generateAdapter = true) data class Burger(val name:
String, val price: Int) val moshi = Moshi.Builder().build() val burgerAdapter = moshi.adapter(Burger::class.java) burgerAdapter.fromJson( "{\"name\":\"Cheeseburger\",\"price\": 13}" ) @zsmb13 -
Moshi: customization options @JsonClass(generateAdapter = true) data class Burger(val name:
String, val price: Int) val moshi = Moshi.Builder().build() val burgerAdapter = moshi.adapter(Burger::class.java) burgerAdapter.fromJson( "{\"name\":\"Cheeseburger\",\"price\": 13}" ) Burger(name=Cheeseburger, price=13) @zsmb13 -
Moshi: customization options @JsonClass(generateAdapter = true) data class Burger(val name:
String, val price: Int) val moshi = Moshi.Builder().build() val burgerAdapter = moshi.adapter(Burger::class.java) burgerAdapter.fromJson( "{\"name\":\"Cheeseburger\",\"price\": 13,\"rating\":4.2}" ) Burger(name=Cheeseburger, price=13) @zsmb13 -
@JsonClass(generateAdapter = true) data class Burger(val name: String, val price:
Int) val moshi = Moshi.Builder().build() val burgerAdapter = moshi.adapter(Burger::class.java) .failOnUnknown() burgerAdapter.fromJson( "{\"name\":\"Cheeseburger\",\"price\": 13,\"rating\":4.2}" ) Moshi: customization options @zsmb13 -
@JsonClass(generateAdapter = true) data class Burger(val name: String, val price:
Int) val moshi = Moshi.Builder().build() val burgerAdapter = moshi.adapter(Burger::class.java) .failOnUnknown() burgerAdapter.fromJson( "{\"name\":\"Cheeseburger\",\"price\": 13,\"rating\":4.2}" ) Moshi: customization options Exception in thread "main" com.squareup.moshi.JsonDataException: Cannot skip unexpected NAME at $.rating @zsmb13 -
@JsonClass(generateAdapter = true) data class Burger(val name: String, val price:
Int) @JsonClass(generateAdapter = true) data class Restaurant(val menu: List<Burger>) @zsmb13 Moshi: malformed data -
@JsonClass(generateAdapter = true) data class Burger(val name: String, val price:
Int) @JsonClass(generateAdapter = true) data class Restaurant(val menu: List<Burger>) val json = """{ "menu": [ { "name": "Classic", "price": 10 }, { "name": "Cheeseburger", "price": 13 }, { "name": "Double", "price": "crocodile" } ] }""" @zsmb13 Moshi: malformed data -
@JsonClass(generateAdapter = true) data class Burger(val name: String, val price:
Int) @JsonClass(generateAdapter = true) data class Restaurant(val menu: List<Burger>) val json = """{ "menu": [ { "name": "Classic", "price": 10 }, { "name": "Cheeseburger", "price": 13 }, { "name": "Double", "price": "crocodile" } ] }""" val restaurant = adapter.fromJson(json, Restaurant::class.java) @zsmb13 Moshi: malformed data -
Moshi: malformed data @JsonClass(generateAdapter = true) data class Burger(val name:
String, val price: Int) @JsonClass(generateAdapter = true) data class Restaurant(val menu: List<Burger>) val json = """{ "menu": [ { "name": "Classic", "price": 10 }, { "name": "Cheeseburger", "price": 13 }, { "name": "Double", "price": "crocodile" } ] }""" val restaurant = adapter.fromJson(json, Restaurant::class.java) Exception in thread "main" com.squareup.moshi.JsonDataException: Expected an int but was crocodile at path $.menu[2].price @zsmb13 -
val json = val restaurant = adapter.fromJson(json, Restaurant::class.java) """{ "menu":
[ { "name": "Classic", "price": 10 }, { "name": "Cheeseburger", "price": 13 }, { "name": "Double", "price": "crocodile" } ] }""" Moshi: malformed data @JsonClass(generateAdapter = true) data class Burger(val name: String, val price: Int) @JsonClass(generateAdapter = true) data class Restaurant(val menu: List<Burger>) Exception in thread "main" com.squareup.moshi.JsonDataException: Expected an int but was crocodile at path $.menu[2].price @zsmb13 -
"""{ "menu": [ { "name": "Classic", "price": 10 }, {
"name": "Cheeseburger", "price": 13 }, { "name": "Double", "price": "crocodile" } ] }""" Exception in thread "main" com.squareup.moshi.JsonDataException: Expected an int but was crocodile at path $.menu[2].price @zsmb13 -
Exception in thread "main" com.squareup.moshi.JsonDataException: Expected an int but was
crocodile at path $.menu[2].price """{ "menu": [ { "name": "Classic", "price": 10 }, { "name": "Cheeseburger", "price": 13 }, { "name": "Double", "price": "crocodile" } ] }""" @zsmb13 -
Exception in thread "main" com.squareup.moshi.JsonDataException: Expected an int but was
crocodile at path $.menu[2].price """{ "menu": [ { "name": "Classic", "price": 10 }, { "name": "Cheeseburger", "price": 13 }, { "name": "Double", "price": "crocodile" } ] }""" @zsmb13 -
Exception in thread "main" com.squareup.moshi.JsonDataException: Expected an int but was
crocodile at path $.menu[2].price """{ "menu": [ { "name": "Classic", "price": 10 }, { "name": "Cheeseburger", "price": 13 }, { "name": "Double", "price": "crocodile" } ] }""" @zsmb13 -
Moshi: malformed data @JsonClass(generateAdapter = true) data class Burger(val name:
String, val price: Int) @JsonClass(generateAdapter = true) data class Restaurant(val menu: List<Burger>) val json = val restaurant = adapter.fromJson(json, Restaurant::class.java) """{ "menu": [ { "name": "Classic", "price": 10 }, { "name": "Cheeseburger", "price": 13 }, { "name": "Double", "price": "crocodile" } ] }""" Exception in thread "main" com.squareup.moshi.JsonDataException: Expected an int but was crocodile at path $.menu[2].price @zsmb13 -
Moshi: malformed data @JsonClass(generateAdapter = true) data class Burger(val name:
String, val price: Int) @JsonClass(generateAdapter = true) data class Restaurant(val menu: List<Burger>) val json = val restaurant = adapter.fromJson(json, Restaurant::class.java) """{ "menu": [ { "name": "Classic", "price": 10 }, { "name": "Cheeseburger", "price": 13 }, { "name": "Double", "price": "crocodile" } ] }""" Exception in thread "main" com.squareup.moshi.JsonDataException: Expected an int but was crocodile at path $.menu[2].price Exception in thread "main" com.google.gson.JsonSyntaxException: java.lang.NumberFormatException: For input string: "crocodile" @zsmb13 -
Moshi: malformed data @JsonClass(generateAdapter = true) data class Burger(val name:
String, val price: Int) @JsonClass(generateAdapter = true) data class Restaurant(val menu: List<Burger>) val json = val restaurant = adapter.fromJson(json, Restaurant::class.java) """{ "menu": [ { "name": "Classic", "price": 10 }, { "name": "Cheeseburger", "price": 13 }, { "name": "Double", "price": "" } ] }""" @zsmb13 Exception in thread "main" com.squareup.moshi.JsonDataException: Expected an int but was at path $.menu[2].price Exception in thread "main" com.google.gson.JsonSyntaxException: java.lang.NumberFormatException: For input string: "" -
Retrofit.Builder() .baseUrl("...") .addConverterFactory(MoshiConverterFactory.create()) .build() def retrofit_version = '2.7.1' implementation "com.squareup.retrofit2:retrofit:$retrofit_version"
implementation "com.squareup.retrofit2:converter-moshi:$retrofit_version" @zsmb13 Moshi: Retrofit integration -
Retrofit.Builder() .baseUrl("...") .addConverterFactory(MoshiConverterFactory.create()) .build() def retrofit_version = '2.7.1' implementation "com.squareup.retrofit2:retrofit:$retrofit_version"
implementation "com.squareup.retrofit2:converter-moshi:$retrofit_version" val moshi = Moshi.Builder() .add(KotlinJsonAdapterFactory()) .add(...) .build() @zsmb13 Moshi: Retrofit integration -
Retrofit.Builder() .baseUrl("...") .addConverterFactory(MoshiConverterFactory.create( .build() def retrofit_version = '2.7.1' implementation "com.squareup.retrofit2:retrofit:$retrofit_version"
implementation "com.squareup.retrofit2:converter-moshi:$retrofit_version" val moshi = Moshi.Builder() .add(KotlinJsonAdapterFactory()) .add(...) .build() @zsmb13 Moshi: Retrofit integration )) moshi -
More Moshi… •Other built-in adapters •Intermediate objects while parsing •Annotated
types •Parsing arrays and other generic types •A lot more under-the-hood details! @zsmb13 -
Resources •A Few Ok Libraries (Jake Wharton, Square) – Droidcon
Montreal 2015 • https://www.youtube.com/watch?v=WvyScM_S88c •Upgrading to Moshi (Eric Cochran, Pinterest) - droidcon NYC 2017 • https://www.youtube.com/watch?v=xHFs7cccnXU •JSON Explained (Jesse Wilson, Square) – Chicago Roboto 2019 • https://www.youtube.com/watch?v=1PwdqkKDCSo @zsmb13 -
Resources •Moshi, another JSON Processor (Jesse Wilson) • https://developer.squareup.com/blog/moshi-another-json-processor/ •Exploring
Moshi’s codegen (Zac Sweers) • https://www.zacsweers.dev/exploring-moshis-kotlin-code-gen/ •Reflection Machines (Jesse Wilson) • https://publicobject.com/2016/03/24/reflection-machines/ •Advanced JSON parsing techniques using Moshi and Kotlin (Christophe Beyls) • https://medium.com/@BladeCoder/advanced-json-parsing-techniques- using-moshi-and-kotlin-daf56a7b963d @zsmb13 -
zsmb.co/talks zsmb13 Márton Braun › Reflection or codegen › Kotlin
friendly › Really fast › Great failure modes Say “hi” to Moshi