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

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

Marton Braun

January 30, 2020
Tweet

More Decks by Marton Braun

Other Decks in Technology

Transcript

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

    View Slide

  2. View Slide

  3. Serialization
    @zsmb13

    View Slide

  4. JSON
    Serialization
    @zsmb13

    View Slide

  5. @zsmb13

    View Slide

  6. @zsmb13

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  14. Gson
    val gson = Gson()
    : built-in types
    @zsmb13

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  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

    View Slide

  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

    View Slide

  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

    View Slide

  21. Gson: field naming
    val gson = Gson()
    @zsmb13

    View Slide

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

    View Slide

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

    View Slide

  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

    View Slide

  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

    View Slide

  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

    View Slide

  27. Gson: missing data
    Gson()
    val gson =
    @zsmb13

    View Slide

  28. data class Burger(
    val name: String,
    val price: Int
    )
    Gson: missing data
    Gson()
    val gson =
    @zsmb13

    View Slide

  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

    View Slide

  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

    View Slide

  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

    View Slide

  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

    View Slide

  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

    View Slide

  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

    View Slide

  35. val gson = Gson()
    Gson: malformed data
    @zsmb13

    View Slide

  36. val gson = Gson()
    Gson: malformed data
    data class Burger(val name: String, val price: Int)
    data class Restaurant(val menu: List)
    @zsmb13

    View Slide

  37. val gson = Gson()
    Gson: malformed data
    data class Burger(val name: String, val price: Int)
    data class Restaurant(val menu: List)
    val json = """{
    "menu": [
    { "name": "Classic", "price": 10 },
    { "name": "Cheeseburger", "price": 13 },
    { "name": "Double", "price": "crocodile" }
    ]
    }"""
    @zsmb13

    View Slide

  38. val gson = Gson()
    Gson: malformed data
    data class Burger(val name: String, val price: Int)
    data class Restaurant(val menu: List)
    val json = """{
    "menu": [
    { "name": "Classic", "price": 10 },
    { "name": "Cheeseburger", "price": 13 },
    { "name": "Double", "price": "crocodile" }
    ]
    }"""
    @zsmb13

    View Slide

  39. val gson = Gson()
    Gson: malformed data
    data class Burger(val name: String, val price: Int)
    data class Restaurant(val menu: List)
    val json = """{
    "menu": [
    { "name": "Classic", "price": 10 },
    { "name": "Cheeseburger", "price": 13 },
    { "name": "Double", "price": "crocodile" }
    ]
    }"""
    val restaurant = gson.fromJson(json, Restaurant::class.java)
    @zsmb13

    View Slide

  40. Gson: malformed data
    val gson = Gson()
    data class Burger(val name: String, val price: Int)
    data class Restaurant(val menu: List)
    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

    View Slide

  41. val gson = Gson()
    Gson: malformed data
    data class Burger(val name: String, val price: Int)
    data class Restaurant(val menu: List)
    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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  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

    View Slide

  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

    View Slide

  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

    View Slide

  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

    View Slide

  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

    View Slide

  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 =
    moshi.adapter(JavaBurger::class.java)
    @zsmb13

    View Slide

  53. // ...
    Moshi 101
    val moshi = Moshi.Builder().build()
    }
    public class JavaBurger {
    private String name;
    private int price;
    val burgerAdapter: JsonAdapter =
    moshi.adapter(JavaBurger::class.java)
    val json = burgerAdapter.toJson(JavaBurger("Classic", 10))
    @zsmb13

    View Slide

  54. // ...
    Moshi 101
    val moshi = Moshi.Builder().build()
    }
    public class JavaBurger {
    private String name;
    private int price;
    val burgerAdapter: JsonAdapter =
    moshi.adapter(JavaBurger::class.java)
    val json = burgerAdapter.toJson(JavaBurger("Classic", 10))
    {"name":"Classic","price":10}
    @zsmb13

    View Slide

  55. // ...
    Moshi 101
    val moshi = Moshi.Builder().build()
    }
    public class JavaBurger {
    private String name;
    private int price;
    val burgerAdapter: JsonAdapter =
    moshi.adapter(JavaBurger::class.java)
    val json = burgerAdapter.toJson(JavaBurger("Classic", 10))
    {"name":"Classic","price":10}
    val burger = burgerAdapter.fromJson(json)
    @zsmb13

    View Slide

  56. Burger::class.java)
    val moshi = Moshi.Builder().build()
    (val : String, val : Int)
    val burgerAdapter: JsonAdapter =
    moshi.adapter(
    Burger
    class name price
    @zsmb13
    Moshi ft. Kotlin

    View Slide

  57. Burger::class.java)
    val moshi = Moshi.Builder().build()
    (val : String, val : Int)
    val burgerAdapter: JsonAdapter =
    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.

    View Slide

  58. Moshi ft. Kotlin
    val moshi = Moshi.Builder().build()
    val burgerAdapter: JsonAdapter =
    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.

    View Slide

  59. Moshi ft. Kotlin
    val moshi = Moshi.Builder().build()
    val burgerAdapter: JsonAdapter =
    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.

    View Slide

  60. val moshi = Moshi.Builder().build()
    val burgerAdapter: JsonAdapter =
    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.

    View Slide

  61. val moshi = Moshi.Builder().build()
    val burgerAdapter: JsonAdapter =
    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

    View Slide

  62. val moshi = Moshi.Builder().build()
    Moshi: reflection
    val burgerAdapter: JsonAdapter =
    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

    View Slide

  63. val moshi = Moshi.Builder()
    Moshi: reflection
    val burgerAdapter: JsonAdapter =
    moshi.adapter(Burger::class.java)
    class Burger(val name: String, val price: Int)
    .build()
    @zsmb13

    View Slide

  64. Moshi: reflection
    .add(KotlinJsonAdapterFactory())
    val burgerAdapter: JsonAdapter =
    moshi.adapter(Burger::class.java)
    class Burger(val name: String, val price: Int)
    .build()
    val moshi = Moshi.Builder()
    @zsmb13

    View Slide

  65. Moshi: reflection
    .add(KotlinJsonAdapterFactory())
    val burgerAdapter: JsonAdapter =
    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

    View Slide

  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

    View Slide

  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.

    View Slide

  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

    View Slide

  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

    View Slide

  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

    View Slide

  71. Moshi: codegen
    class Burger(val name: String, val price: Int)
    @zsmb13

    View Slide

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

    View Slide

  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

    View Slide

  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

    View Slide

  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

    View Slide

  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

    View Slide

  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

    View Slide

  78. @FromJson
    fun fromJson(reader: JsonReader): Burger? {
    }
    @zsmb13
    Moshi: custom adapters

    View Slide

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

    View Slide

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

    View Slide

  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

    View Slide

  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

    View Slide

  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

    View Slide

  84. class BurgerJsonAdapter(
    moshi: Moshi
    ) : JsonAdapter() {
    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 =
    moshi.adapter(String::class.java, emptySet(), "name")
    private val intAdapter: JsonAdapter =
    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

    View Slide

  85. class BurgerJsonAdapter(
    moshi: Moshi
    ) : JsonAdapter() {
    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 =
    moshi.adapter(String::class.java, emptySet(), "name")
    private val intAdapter: JsonAdapter =
    moshi.adapter(Int::class.java, emptySet(), "price")
    Moshi’s generated adapter
    @zsmb13

    View Slide

  86. private val intAdapter: JsonAdapter =
    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

    View Slide

  87. private val intAdapter: JsonAdapter =
    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

    View Slide

  88. private val intAdapter: JsonAdapter =
    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

    View Slide

  89. private val intAdapter: JsonAdapter =
    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

    View Slide

  90. private val intAdapter: JsonAdapter =
    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

    View Slide

  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 =
    moshi.adapter(String::class.java, emptySet(), "name")
    private val intAdapter: JsonAdapter =
    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

    View Slide

  92. private val intAdapter: JsonAdapter =
    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

    View Slide

  93. private val options: JsonReader.Options =
    JsonReader.Options.of("name", "price")
    private val stringAdapter: JsonAdapter =
    moshi.adapter(String::class.java, emptySet(), "name")
    private val intAdapter: JsonAdapter =
    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

    View Slide

  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

    View Slide

  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

    View Slide

  96. val moshi = Moshi.Builder().build()
    val adapter: JsonAdapter =
    moshi.adapter(Date::class.java)
    @zsmb13
    Moshi: built-in types

    View Slide

  97. Moshi: built-in types
    val moshi = Moshi.Builder().build()
    val adapter: JsonAdapter =
    moshi.adapter(Date::class.java)
    Exception in thread "main"
    java.lang.IllegalArgumentException:
    Platform class java.util.Date
    requires explicit JsonAdapter to be
    registered
    @zsmb13

    View Slide

  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 =
    moshi.adapter(Date::class.java)
    @zsmb13

    View Slide

  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 =
    moshi.adapter(Date::class.java)
    @zsmb13

    View Slide

  100. val adapter: JsonAdapter =
    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

    View Slide

  101. val adapter: JsonAdapter =
    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

    View Slide

  102. @zsmb13
    Moshi: field naming

    View Slide

  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

    View Slide

  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

    View Slide

  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

    View Slide

  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

    View Slide

  107. @JsonClass(generateAdapter = true)
    data class Burger(
    val name: String,
    val price: Int
    )
    @zsmb13
    Moshi: missing data

    View Slide

  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

    View Slide

  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

    View Slide

  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

    View Slide

  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

    View Slide

  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

    View Slide

  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

    View Slide

  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

    View Slide

  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

    View Slide

  116. Shameless plug
    https://zsmb.co/primaries-matter-a-discussion-of-constructors/
    @zsmb13

    View Slide

  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

    View Slide

  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

    View Slide

  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

    View Slide

  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
    @zsmb13

    View Slide

  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

    View Slide

  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

    View Slide

  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

    View Slide

  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

    View Slide

  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

    View Slide

  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

    View Slide

  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

    View Slide

  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

    View Slide

  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

    View Slide

  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

    View Slide

  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

    View Slide

  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

    View Slide

  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

    View Slide

  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

    View Slide

  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

    View Slide

  136. Moshi: malformed data
    @zsmb13

    View Slide

  137. @JsonClass(generateAdapter = true)
    data class Burger(val name: String, val price: Int)
    @JsonClass(generateAdapter = true)
    data class Restaurant(val menu: List)
    @zsmb13
    Moshi: malformed data

    View Slide

  138. @JsonClass(generateAdapter = true)
    data class Burger(val name: String, val price: Int)
    @JsonClass(generateAdapter = true)
    data class Restaurant(val menu: List)
    val json = """{
    "menu": [
    { "name": "Classic", "price": 10 },
    { "name": "Cheeseburger", "price": 13 },
    { "name": "Double", "price": "crocodile" }
    ]
    }"""
    @zsmb13
    Moshi: malformed data

    View Slide

  139. @JsonClass(generateAdapter = true)
    data class Burger(val name: String, val price: Int)
    @JsonClass(generateAdapter = true)
    data class Restaurant(val menu: List)
    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

    View Slide

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

    View Slide

  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)
    Exception in thread "main"
    com.squareup.moshi.JsonDataException:
    Expected an int but was crocodile at
    path $.menu[2].price
    @zsmb13

    View Slide

  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

    View Slide

  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

    View Slide

  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

    View Slide

  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

    View Slide

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

    View Slide

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

    View Slide

  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)
    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: ""

    View Slide

  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

    View Slide

  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

    View Slide

  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

    View Slide

  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

    View Slide

  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

    View Slide

  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

    View Slide

  155. zsmb.co/talks
    zsmb13
    Márton Braun
    › Reflection or codegen
    › Kotlin friendly
    › Really fast
    › Great failure modes
    Say “hi” to Moshi

    View Slide