$30 off During Our Annual Pro Sale. View Details »

Say "hi" to Moshi (DevFest Romania 2021)

Marton Braun
November 13, 2021

Say "hi" to Moshi (DevFest Romania 2021)

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.

More resources and info: https://zsmb.co/talks/say-hi-to-moshi/

Marton Braun

November 13, 2021
Tweet

More Decks by Marton Braun

Other Decks in Programming

Transcript

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

    View Slide

  2. @zsmb13

    View Slide

  3. Serialization
    @zsmb13

    View Slide

  4. JSON
    Serialization
    @zsmb13

    View Slide

  5. JSON
    Serialization
    on the JVM
    @zsmb13

    View Slide

  6. @zsmb13

    View Slide

  7. @zsmb13

    View Slide

  8. @zsmb13

    View Slide

  9. @zsmb13

    View Slide

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

    View Slide

  11. Gson 101
    @zsmb13

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  20. Gson
    val gson = Gson()
    gson.toJson(Date())
    : built-in types
    @zsmb13
    "Nov 13, 2021 : : AM"
    10 55
    22

    View Slide

  21. Gson
    val gson = Gson()
    gson.toJson(Date())
    : built-in types
    @zsmb13
    "Nov 13, 2021 : : AM"
    10 56
    11

    View Slide

  22. Gson
    val gson = Gson()
    gson.toJson(Date())
    : built-in types
    @zsmb13
    gson.toJson(Calendar.getInstance())
    "Nov 13, 2021 : : AM"
    10 56
    11
    {"year":2021,"month":10,"dayOfMonth":13,"hourOfDay":
    "minute": ,"second": }
    ,
    22
    10 58

    View Slide

  23. Gson
    val gson = Gson()
    gson.toJson(Date())
    "Nov 13, 2021 : : AM"
    gson.toJson(Calendar.getInstance())
    {"year":2021,"month":10,"dayOfMonth":13,"hourOfDay":
    "minute": ,"second": }
    : built-in types
    @zsmb13
    ,
    11
    10 56
    10 56
    11

    View Slide

  24. Gson
    val gson = Gson()
    gson.toJson(Date())
    gson.toJson(Calendar.getInstance())
    : built-in types
    @zsmb13
    gson.toJson(Random())
    "Nov 13, 2021 : : AM"
    10 56
    11
    {"year":2021,"month":10,"dayOfMonth":13,"hourOfDay":
    "minute": ,"second": }
    ,
    11
    10 56

    View Slide

  25. Gson
    val gson = Gson()
    gson.toJson(Date())
    gson.toJson(Calendar.getInstance())
    : built-in types
    @zsmb13
    gson.toJson(Random())
    {"seed":{"value":137564286790437},"nextNextGaussian":0.0,
    "haveNextNextGaussian":false}
    "Nov 13, 2021 : : AM"
    10 56
    11
    {"year":2021,"month":10,"dayOfMonth":13,"hourOfDay":
    "minute": ,"second": }
    ,
    11
    10 56

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  29. 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

  30. 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))
    @zsmb13

    View Slide

  31. 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

  32. GsonBuilder()
    .setFieldNamingPolicy(
    FieldNamingPolicy.LOWER_CASE_WITH_UNDERSCORES)
    .create()
    Gson: field naming
    gson.toJson(Burger("Classic", 10, 13))
    {"name":"Classic","total_cost":10,"sales_price":13}
    val gson =
    data class Burger(
    val name: String,
    val totalCost: Int,
    val salesPrice: Int
    )
    @zsmb13

    View Slide

  33. @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

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

    View Slide

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

    View Slide

  36. 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

  37. 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

  38. 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

  39. 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

  40. 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

  41. 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

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

    View Slide

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

    View Slide

  44. 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

  45. 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

  46. 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

  47. 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

  48. 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)
    Exception in thread "main"
    com.google.gson.JsonSyntaxException:
    java.lang.NumberFormatException:
    For input string: "crocodile"
    @zsmb13

    View Slide

  49. 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

  50. View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  55. Moshi 101
    @zsmb13

    View Slide

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

    View Slide

  57. 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

  58. // ...
    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

  59. // ...
    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

  60. // ...
    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

  61. // ...
    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

  62. // ...
    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

  63. // ...
    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

  64. // ...
    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

  65. // ...
    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

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

    View Slide

  67. 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

  68. Burger::class.java)
    val moshi = Moshi.Builder().build()
    (val : String, val : Int)
    val burgerAdapter: JsonAdapter =
    moshi.adapter(
    Burger
    class name price
    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.
    @zsmb13

    View Slide

  69. Moshi ft. Kotlin
    val moshi = Moshi.Builder().build()
    val burgerAdapter: JsonAdapter =
    moshi.adapter(Burger::class.java)
    class Burger(val name: String, val price: Int)
    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.
    @zsmb13

    View Slide

  70. Moshi ft. Kotlin
    val moshi = Moshi.Builder().build()
    val burgerAdapter: JsonAdapter =
    moshi.adapter(Burger::class.java)
    class Burger(val name: String, val price: Int)
    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.
    @zsmb13

    View Slide

  71. val moshi = Moshi.Builder().build()
    val burgerAdapter: JsonAdapter =
    moshi.adapter(Burger::class.java)
    class Burger(val name: String, val price: Int)
    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.
    @zsmb13

    View Slide

  72. 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.12.0'
    implementation "com.squareup.moshi:moshi:$moshi_version"
    @zsmb13
    Moshi: reflection

    View Slide

  73. 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.12.0'
    implementation "com.squareup.moshi:moshi:$moshi_version"
    implementation "com.squareup.moshi:moshi-kotlin:$moshi_version"
    @zsmb13

    View Slide

  74. 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

  75. 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

  76. 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))
    .build()
    val moshi = Moshi.Builder()
    @zsmb13

    View Slide

  77. 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))
    .build()
    val moshi = Moshi.Builder()
    @zsmb13
    {"name":"Classic","price":10}

    View Slide

  78. 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))
    .build()
    val moshi = Moshi.Builder()
    @zsmb13
    {"name":"Classic","price":10}
    val burger = burgerAdapter.fromJson(json)

    View Slide

  79. Moshi: reflection
    def moshi_version = '1.12.0'
    implementation "com.squareup.moshi:moshi:$moshi_version"
    implementation "com.squareup.moshi:moshi-kotlin:$moshi_version"
    @zsmb13

    View Slide

  80. Moshi: reflection
    def moshi_version = '1.12.0'
    implementation "com.squareup.moshi:moshi:$moshi_version"
    implementation "com.squareup.moshi:moshi-kotlin:$moshi_version"
    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.
    @zsmb13

    View Slide

  81. implementation moshi-kotlin:$moshi_version"
    "com.squareup.moshi:
    def moshi_version = '1.12.0'
    implementation "com.squareup.moshi:moshi:$moshi_version"
    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-
    @zsmb13
    codegen

    View Slide

  82. def moshi_version = '1.12.0'
    implementation "com.squareup.moshi:moshi:$moshi_version"
    kapt moshi-kotlin-codegen:$moshi_version"
    "com.squareup.moshi:
    @zsmb13
    Moshi: codegen

    View Slide

  83. Moshi: codegen
    def moshi_version = '1.12.0'
    implementation "com.squareup.moshi:moshi:$moshi_version"
    kapt "com.squareup.moshi:moshi-kotlin-codegen:$moshi_version"
    @zsmb13

    View Slide

  84. dependencies {
    }
    Moshi: codegen
    apply plugin: 'kotlin'
    apply plugin: 'kotlin-kapt'
    def moshi_version = '1.12.0'
    implementation "com.squareup.moshi:moshi:$moshi_version"
    kapt "com.squareup.moshi:moshi-kotlin-codegen:$moshi_version"
    @zsmb13

    View Slide

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

    View Slide

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

    View Slide

  87. @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

  88. dependencies {
    def moshi_version = '1.12.0'
    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

  89. Moshi: mixed use
    dependencies {
    def moshi_version = '1.12.0'
    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

  90. dependencies {
    def moshi_version = '1.12.0'
    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

  91. 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

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

    View Slide

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

    View Slide

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

    View Slide

  95. 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

  96. 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

  97. @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

  98. 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

  99. 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")
    @zsmb13
    Moshi’s generated adapter

    View Slide

  100. 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

  101. 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

  102. 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

  103. 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

  104. 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

  105. 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

  106. 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

  107. 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

  108. 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

  109. 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

  110. 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

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

    View Slide

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

    View Slide

  113. 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

  114. Moshi: built-in types
    def moshi_version = '1.12.0'
    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

  115. Moshi: built-in types
    def moshi_version = '1.12.0'
    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

  116. def moshi_version = '1.12.0'
    implementation "com.squareup.moshi:moshi:$moshi_version"
    implementation "com.squareup.moshi:moshi-adapters:$moshi_version"
    kapt "com.squareup.moshi:moshi-kotlin-codegen:$moshi_version"
    val adapter: JsonAdapter =
    moshi.adapter(Date::class.java)
    .build()
    val moshi = Moshi.Builder()
    @zsmb13
    Moshi: built-in types

    View Slide

  117. val adapter: JsonAdapter =
    moshi.adapter(Date::class.java)
    def moshi_version = '1.12.0'
    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
    2021-11-13T : : Z
    22 08 54

    View Slide

  118. val adapter: JsonAdapter =
    moshi.adapter(Date::class.java)
    def moshi_version = '1.12.0'
    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())
    2021-11-13T : : Z
    .build()
    val moshi = Moshi.Builder()
    @zsmb13
    Moshi: built-in types
    11 10 56

    View Slide

  119. @zsmb13
    Moshi: field naming

    View Slide

  120. @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

  121. @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

  122. @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

  123. @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

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

    View Slide

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

    View Slide

  126. @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

  127. @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

  128. @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

  129. @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

  130. @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

  131. @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

  132. @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

  133. 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

  134. 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

  135. 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

  136. 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

  137. 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

  138. 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

  139. 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

  140. 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

  141. 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

  142. 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

  143. 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

  144. 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
    JsonAdapter

    View Slide

  145. 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

  146. 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

  147. 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

  148. 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

  149. 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

  150. 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

  151. 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

  152. 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

  153. 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

  154. 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}"
    )
    @zsmb13

    View Slide

  155. 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

  156. @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

  157. @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

  158. Moshi: malformed data
    @zsmb13

    View Slide

  159. @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

  160. @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

  161. @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

  162. 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

  163. 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

  164. """{
    "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

  165. 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

  166. 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

  167. 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

  168. 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

  169. 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

  170. 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

  171. 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": "" }
    ]
    }"""
    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: ""
    @zsmb13

    View Slide

  172. @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": "" }
    ]
    }"""
    Moshi: malformed data
    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: ""
    @zsmb13

    View Slide

  173. def retrofit_version = '2.9.0'
    implementation "com.squareup.retrofit2:retrofit:$retrofit_version"
    @zsmb13
    Moshi: Retrofit integration

    View Slide

  174. def retrofit_version = '2.9.0'
    implementation "com.squareup.retrofit2:retrofit:$retrofit_version"
    implementation "com.squareup.retrofit2:converter-moshi:$retrofit_version"
    @zsmb13
    Moshi: Retrofit integration

    View Slide

  175. Retrofit.Builder()
    .baseUrl("...")
    .addConverterFactory(MoshiConverterFactory.create())
    .build()
    def retrofit_version = '2.9.0'
    implementation "com.squareup.retrofit2:retrofit:$retrofit_version"
    implementation "com.squareup.retrofit2:converter-moshi:$retrofit_version"
    @zsmb13
    Moshi: Retrofit integration

    View Slide

  176. Retrofit.Builder()
    .baseUrl("...")
    .addConverterFactory(MoshiConverterFactory.create())
    .build()
    def retrofit_version = '2.9.0'
    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

  177. def retrofit_version = '2.9.0'
    implementation "com.squareup.retrofit2:retrofit:$retrofit_version"
    implementation "com.squareup.retrofit2:converter-moshi:$retrofit_version"
    val moshi = Moshi.Builder()
    .add(KotlinJsonAdapterFactory())
    .add(...)
    .build()
    @zsmb13
    Moshi: Retrofit integration
    ))
    Retrofit.Builder()
    .baseUrl("...")
    .addConverterFactory(MoshiConverterFactory.create(
    .build()

    View Slide

  178. Retrofit.Builder()
    .baseUrl("...")
    .addConverterFactory(MoshiConverterFactory.create(
    .build()
    def retrofit_version = '2.9.0'
    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

  179. More Moshi…
    •Other built-in adapters
    •Intermediate objects while parsing
    •Annotated types
    •Parsing arrays and other generic types
    •KSP support
    •A lot more under-the-hood details!
    @zsmb13

    View Slide

  180. 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

  181. 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

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

    View Slide