Say "hi" to Moshi (Android Budapest meetup - January 2020)

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

4047c64e3a1e2f81addd4ba675ddc451?s=128

Marton Braun

January 30, 2020
Tweet

Transcript

  1. Say “hi” to zsmb.co zsmb13 Márton Braun Moshi

  2. None
  3. Serialization @zsmb13

  4. JSON Serialization @zsmb13

  5. @zsmb13

  6. @zsmb13

  7. Gson github.com/google/gson @zsmb13

  8. Gson 101 val burger: Burger = Gson().fromJson("{...}", Burger::class.java) val json:

    String = Gson().toJson(Burger()) @zsmb13
  9. Gson 101 val burger: Burger = Gson().fromJson("{...}", Burger::class.java) val json:

    String = Gson().toJson(Burger()) @zsmb13
  10. Gson 101 val burger: Burger = Gson().fromJson("{...}", Burger::class.java) val json:

    String = Gson().toJson(Burger()) @zsmb13
  11. Gson 101 val gson = Gson() val burger: Burger =

    Gson().fromJson("{...}", Burger::class.java) val json: String = Gson().toJson(Burger()) @zsmb13
  12. Gson 101 val gson = Gson() val burger: Burger =

    gson.fromJson("{...}", Burger::class.java) val json: String = gson.toJson(Burger()) @zsmb13
  13. Gson val gson = Gson() val burger: Burger = gson.fromJson("{...}",

    Burger::class.java) val json: String = gson.toJson(Burger()) 101 @zsmb13
  14. Gson val gson = Gson() : built-in types @zsmb13

  15. Gson val gson = Gson() gson.toJson(Date()) : built-in types @zsmb13

  16. Gson val gson = Gson() gson.toJson(Date()) "Jan 30, 2020 7:03:50

    PM" : built-in types @zsmb13
  17. Gson val gson = Gson() gson.toJson(Date()) "Jan 30, 2020 7:03:50

    PM" gson.toJson(Calendar.getInstance()) : built-in types @zsmb13
  18. 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
  19. 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
  20. 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
  21. Gson: field naming val gson = Gson() @zsmb13

  22. Gson: field naming val gson = GsonBuilder() .create() @zsmb13

  23. val gson = GsonBuilder() .setFieldNamingPolicy( FieldNamingPolicy.LOWER_CASE_WITH_UNDERSCORES) .create() Gson: field naming

    @zsmb13
  24. 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
  25. 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
  26. @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
  27. Gson: missing data Gson() val gson = @zsmb13

  28. data class Burger( val name: String, val price: Int )

    Gson: missing data Gson() val gson = @zsmb13
  29. 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
  30. 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
  31. 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
  32. 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
  33. 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
  34. 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
  35. val gson = Gson() Gson: malformed data @zsmb13

  36. val gson = Gson() Gson: malformed data data class Burger(val

    name: String, val price: Int) data class Restaurant(val menu: List<Burger>) @zsmb13
  37. 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
  38. 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
  39. 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
  40. 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
  41. 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
  42. Say “hi” to zsmb.co zsmb13 Márton Braun Moshi

  43. Moshi github.com/square/moshi @zsmb13

  44. Okio github.com/square/okio Retrofit github.com/square/retrofit Moshi github.com/square/moshi OkHttp github.com/square/okhttp @zsmb13

  45. Moshi github.com/square/moshi @zsmb13

  46. Moshi 101 def moshi_version = '1.9.2' implementation "com.squareup.moshi:moshi:$moshi_version" @zsmb13

  47. 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
  48. // ... 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
  49. // ... 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
  50. // ... 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
  51. // ... Moshi 101 val moshi = Moshi.Builder().build() val json

    = moshi.toJson(JavaBurger("Classic", 10)) } public class JavaBurger { private String name; private int price; @zsmb13
  52. // ... 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
  53. // ... 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
  54. // ... 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
  55. // ... 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
  56. 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
  57. 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.
  58. 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.
  59. 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.
  60. 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.
  61. 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
  62. 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
  63. 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
  64. 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
  65. 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
  66. Moshi: reflection def moshi_version = '1.9.2' implementation "com.squareup.moshi:moshi:$moshi_version" implementation "com.squareup.moshi:moshi-kotlin:$moshi_version"

    @zsmb13
  67. 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.
  68. 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
  69. def moshi_version = '1.9.2' implementation "com.squareup.moshi:moshi:$moshi_version" kapt moshi-kotlin-codegen:$moshi_version" "com.squareup.moshi: @zsmb13

    Moshi: codegen
  70. 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
  71. Moshi: codegen class Burger(val name: String, val price: Int) @zsmb13

  72. @JsonClass(generateAdapter = true) class Burger(val name: String, val price: Int)

    @zsmb13 Moshi: codegen
  73. @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
  74. 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
  75. 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
  76. 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
  77. 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
  78. @FromJson fun fromJson(reader: JsonReader): Burger? { } @zsmb13 Moshi: custom

    adapters
  79. Moshi: custom adapters @FromJson fun fromJson(reader: JsonReader): Burger? { var

    name: String? = null var price: Int? = null } @zsmb13
  80. Moshi: custom adapters @FromJson fun fromJson(reader: JsonReader): Burger? { var

    name: String? = null var price: Int? = null reader.beginObject() reader.endObject() } @zsmb13
  81. 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
  82. 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
  83. @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
  84. 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
  85. 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
  86. 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
  87. 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
  88. 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
  89. 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
  90. 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
  91. 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
  92. 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
  93. 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
  94. 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
  95. 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
  96. val moshi = Moshi.Builder().build() val adapter: JsonAdapter<Date> = moshi.adapter(Date::class.java) @zsmb13

    Moshi: built-in types
  97. 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
  98. 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
  99. 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
  100. 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
  101. 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
  102. @zsmb13 Moshi: field naming

  103. @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
  104. @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
  105. @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
  106. @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
  107. @JsonClass(generateAdapter = true) data class Burger( val name: String, val

    price: Int ) @zsmb13 Moshi: missing data
  108. @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
  109. @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
  110. @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
  111. @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
  112. @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
  113. @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
  114. 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
  115. 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
  116. Shameless plug https://zsmb.co/primaries-matter-a-discussion-of-constructors/ @zsmb13

  117. 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
  118. 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
  119. 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
  120. 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
  121. 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
  122. 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
  123. 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
  124. 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
  125. 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
  126. 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
  127. 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
  128. 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
  129. 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
  130. 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
  131. 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
  132. 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
  133. 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
  134. @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
  135. @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
  136. Moshi: malformed data @zsmb13

  137. @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
  138. @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
  139. @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
  140. 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
  141. 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
  142. """{ "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
  143. 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
  144. 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
  145. 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
  146. 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
  147. 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
  148. 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: ""
  149. 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
  150. 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
  151. 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
  152. 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
  153. 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
  154. 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
  155. zsmb.co/talks zsmb13 Márton Braun › Reflection or codegen › Kotlin

    friendly › Really fast › Great failure modes Say “hi” to Moshi