Slide 1

Slide 1 text

Data classes and parsing JSON - A story about converting Models to Kotlin Stefan Medack Zonic03

Slide 2

Slide 2 text

No content

Slide 3

Slide 3 text

Data Classes and Parsing JSON - Stefan Medack (@Zonic03) How to make your PO happy What we have: ▸ API that can not be (easily) changed ▸ JSON can be different (to your expectations) What we want: ▸ Less content related crashes ▸ get rid of tedious, nested null checks ▸ filter „broken“ content

Slide 4

Slide 4 text

Quiz Time How many different ways are there to define a String field in JSON?

Slide 5

Slide 5 text

Data Classes and Parsing JSON - Stefan Medack (@Zonic03) { "field": "has some text" }

Slide 6

Slide 6 text

Data Classes and Parsing JSON - Stefan Medack (@Zonic03) { „field": null }

Slide 7

Slide 7 text

Data Classes and Parsing JSON - Stefan Medack (@Zonic03) { // field is omitted from JSON }

Slide 8

Slide 8 text

Data Classes and Parsing JSON - Stefan Medack (@Zonic03) { "id": "article_1", "title": "Data classes and parsing JSON", "subtitle": "A story about converting Models to Kotlin", "cell_image": { "url": "https://images.kitchenstories.de/example.jpg" }, "author": { "username": "Stefan Medack" }, "comments": [ ... ], "content": [ ... ] ... }

Slide 9

Slide 9 text

Data Classes and Parsing JSON - Stefan Medack (@Zonic03) public class Article { public String id; public String title; public Image cell_image; public User author; public Video video; public String subtitle; public List content; public int like_count; public List comments; public Date published_at; // Constructors, Getters, Setters, ... }

Slide 10

Slide 10 text

Data Classes and Parsing JSON - Stefan Medack (@Zonic03) data class Article( val id: String, val title: String, val cell_image: Image, val author: User, val video: Video? = null, val subtitle: String = EMPTY_STRING, val content: List = listOf(), val like_count: Int = 0, val comments: List = listOf(), val published_at: Date = Date(0) )

Slide 11

Slide 11 text

Data Classes and Parsing JSON - Stefan Medack (@Zonic03) data class Article( val id: String, val title: String, val cell_image: Image, val author: User, val video: Video? = null, val subtitle: String = EMPTY_STRING, val content: List = listOf(), val like_count: Int = 0, val comments: List = listOf(), val published_at: Date = Date(0) )

Slide 12

Slide 12 text

Data Classes and Parsing JSON - Stefan Medack (@Zonic03) data class Article( val id: String, val title: String, val image: Image, val author: User, val video: Video? = null, val subtitle: String = EMPTY_STRING, val content: List = listOf(), val likeCount: Int = 0, val comments: List = listOf(), val publishedAt: Date = Date(0) )

Slide 13

Slide 13 text

Data Classes and Parsing JSON - Stefan Medack (@Zonic03) data class Article( val id: String, val title: String, @Json(name = "cell_image") val image: Image, val author: User, val video: Video? = null, val subtitle: String = EMPTY_STRING, val content: List = listOf(), @Json(name = "like_count") val likeCount: Int = 0, val comments: List = listOf(), @Json(name = "published_at") val publishedAt: Date = Date(0) )

Slide 14

Slide 14 text

Data Classes and Parsing JSON - Stefan Medack (@Zonic03) data class Article( val id: String, val title: String, val image: Image, val author: User, val video: Video? = null, val subtitle: String = EMPTY_STRING, val content: List = listOf(), val likeCount: Int = 0, val comments: List = listOf(), val publishedAt: Date = Date(0) )

Slide 15

Slide 15 text

Data Classes and Parsing JSON - Stefan Medack (@Zonic03) data class Article( val id: String, val title: String, val image: Image, val author: User, val video: Video? = null, val subtitle: String = EMPTY_STRING, val content: List = listOf(), val likeCount: Int = 0, val comments: List = listOf(), val publishedAt: Date = Date(0) )

Slide 16

Slide 16 text

Data Classes and Parsing JSON - Stefan Medack (@Zonic03) data class Article( val id: String, val title: String, val image: Image, val author: User, val video: Video? = null, val subtitle: String = EMPTY_STRING, val content: List = listOf(), val likeCount: Int = 0, val comments: List = listOf(), val publishedAt: Date = Date(0) )

Slide 17

Slide 17 text

Data Classes and Parsing JSON - Stefan Medack (@Zonic03) data class Article( val id: String, val title: String, val image: Image, val author: User, val video: Video? = null, val subtitle: String = EMPTY_STRING, val content: List = listOf(), val likeCount: Int = 0, val comments: List = listOf(), val publishedAt: Date = Date(0) )

Slide 18

Slide 18 text

Data Classes and Parsing JSON - Stefan Medack (@Zonic03) data class Article( val id: String, val title: String, val image: Image, val author: User, val video: Video? = null, val subtitle: String = EMPTY_STRING, val content: List = listOf(), val likeCount: Int = 0, val comments: List = listOf(), val publishedAt: Date = Date(0) )

Slide 19

Slide 19 text

Data Classes and Parsing JSON - Stefan Medack (@Zonic03) data class Article( val id: String, val title: String, val image: Image, val author: User, val video: Video? = null, val subtitle: String = EMPTY_STRING, val content: List = listOf(), val likeCount: Int = 0, val comments: List = listOf(), val publishedAt: Date = Date(0) )

Slide 20

Slide 20 text

Data Classes and Parsing JSON - Stefan Medack (@Zonic03) data class Article( val id: String, val title: String, val image: Image, val author: User, val video: Video? = null, val subtitle: String = EMPTY_STRING, val content: List = listOf(), val likeCount: Int = 0, val comments: List = listOf(), val publishedAt: Date = Date(0) )

Slide 21

Slide 21 text

Data Classes and Parsing JSON - Stefan Medack (@Zonic03) JSON <-> Data Class ▸ Kotson ▸ Jackson ▸ Moshi

Slide 22

Slide 22 text

Data Classes and Parsing JSON - Stefan Medack (@Zonic03) How to setup Moshi 1

Slide 23

Slide 23 text

Data Classes and Parsing JSON - Stefan Medack (@Zonic03) How to setup Moshi implementation „com.squareup.moshi:moshi:1.6.0“ // Version A) implementation „com.squareup.moshi:moshi-kotlin:1.6.0“ // adds kotlin.reflect library !

Slide 24

Slide 24 text

Data Classes and Parsing JSON - Stefan Medack (@Zonic03) How to setup Moshi implementation „com.squareup.moshi:moshi:1.6.0“ // Version A) implementation „com.squareup.moshi:moshi-kotlin:1.6.0“ // Version B) Kapt „com.squareup.moshi:moshi-kotlin-codegen:1.6.0“

Slide 25

Slide 25 text

Data Classes and Parsing JSON - Stefan Medack (@Zonic03) How to setup Moshi 2

Slide 26

Slide 26 text

Data Classes and Parsing JSON - Stefan Medack (@Zonic03) How to setup Moshi val moshi = Moshi.Builder() // "... here be adapters# .build()

Slide 27

Slide 27 text

Data Classes and Parsing JSON - Stefan Medack (@Zonic03) How to setup Moshi - with reflection val moshi = Moshi.Builder() // "... here be adapters# .add(KotlinJsonAdapterFactory()) //constructor default values$ .build() https://github.com/square/moshi/issues/315

Slide 28

Slide 28 text

OR… use version B (B stands for BETTER! %)

Slide 29

Slide 29 text

Data Classes and Parsing JSON - Stefan Medack (@Zonic03) How to setup Moshi - with code generation data class Article( val title: String, val image: Image, // … ) val moshi = Moshi.Builder() // no Kotlin Adapter needed .build()

Slide 30

Slide 30 text

Data Classes and Parsing JSON - Stefan Medack (@Zonic03) How to setup Moshi - with code generation @JsonClass(generateAdapter = true) data class Article( val title: String, val image: Image, // … ) val moshi = Moshi.Builder() // no Kotlin Adapter needed .build()

Slide 31

Slide 31 text

Data Classes and Parsing JSON - Stefan Medack (@Zonic03) How to setup Moshi - with code generation @JsonClass(generateAdapter = true) data class Article( val title: String, val image: Image, // … ) val moshi = Moshi.Builder() // no Kotlin Adapter needed .build() Step 3) use Moshi with Retrofit https://proandroiddev.com/a69c2621708b

Slide 32

Slide 32 text

But is it the end??? sadly, no

Slide 33

Slide 33 text

Data Classes and Parsing JSON - Stefan Medack (@Zonic03) Let’s test https://github.com/stefanmedack/MoshiKotlinExample

Slide 34

Slide 34 text

Data Classes and Parsing JSON - Stefan Medack (@Zonic03) Let’s test ▸ What is Date ! -> Crash & ▸ Not all mandatory fields are present? ! -> Crash & ▸ There can be nulls in non-nullable lists? ! -> Crash &

Slide 35

Slide 35 text

Data Classes and Parsing JSON - Stefan Medack (@Zonic03) val moshi = Moshi.Builder() // "... here be adapters# .build()

Slide 36

Slide 36 text

Data Classes and Parsing JSON - Stefan Medack (@Zonic03) val moshi = Moshi.Builder() .add( Date::class.java, Rfc3339DateJsonAdapter().nullSafe() ) .build()

Slide 37

Slide 37 text

Data Classes and Parsing JSON - Stefan Medack (@Zonic03) Let’s test ▸ What is Date ! -> Successful ✅ ▸ Not all mandatory fields are present? ! -> Crash & ▸ There can be nulls in non-nullable lists? ! -> Crash &

Slide 38

Slide 38 text

Data Classes and Parsing JSON - Stefan Medack (@Zonic03) Let’s test ▸ What is Date ! -> Successful ✅ ▸ Not all mandatory fields are present? ! -> Crash & ▸ There can be nulls in non-nullable lists? ! -> Crash &

Slide 39

Slide 39 text

Data Classes and Parsing JSON - Stefan Medack (@Zonic03) @JsonClass(generateAdapter = true) data class Article( val id: String, val title: String, val image: Image, val author: User, val video: Video? = null, val subtitle: String = EMPTY_STRING, val content: List = listOf(), val likeCount: Int = 0, val comments: List = listOf(), val publishedAt: Date = Date(0) )

Slide 40

Slide 40 text

Data Classes and Parsing JSON - Stefan Medack (@Zonic03) class DefaultOnDataMismatchAdapter private constructor( private val delegate: JsonAdapter, private val defaultValue: T? ) : JsonAdapter() { @Throws(IOException::class) override fun fromJson(reader: JsonReader): T? = try { delegate.fromJsonValue(reader.readJsonValue()) } catch (e: Exception) { Timber.w("Wrongful content - could not parse delegate $delegate") defaultValue } @Throws(IOException::class) override fun toJson(writer: JsonWriter, value: T?) { delegate.toJson(writer, value) } companion object { @JvmStatic fun newFactory(type: Class, defaultValue: T?): JsonAdapter.Factory { return object : JsonAdapter.Factory { override fun create(requestedType: Type, annotations: Set, moshi: Moshi): JsonAdapter<*>? { if (type != requestedType) { return null } val delegate = moshi.nextAdapter(this, type, annotations) return DefaultOnDataMismatchAdapter(delegate, defaultValue) } } } } } https://gist.github.com/stefanmedack/719fd8d905c6be982e1b4f807cbda3fa

Slide 41

Slide 41 text

Data Classes and Parsing JSON - Stefan Medack (@Zonic03) class DefaultOnDataMismatchAdapter private constructor( private val delegate: JsonAdapter, private val defaultValue: T? ) : JsonAdapter() { @Throws(IOException::class) override fun fromJson(reader: JsonReader): T? = try { delegate.fromJsonValue(reader.readJsonValue()) } catch (e: Exception) { Timber.w("Wrongful content - could not parse delegate $delegate") defaultValue } @Throws(IOException::class) override fun toJson(writer: JsonWriter, value: T?) { delegate.toJson(writer, value) } } https://gist.github.com/stefanmedack/719fd8d905c6be982e1b4f807cbda3fa

Slide 42

Slide 42 text

Data Classes and Parsing JSON - Stefan Medack (@Zonic03) class DefaultOnDataMismatchAdapter private constructor( private val delegate: JsonAdapter, private val defaultValue: T? ) : JsonAdapter() { @Throws(IOException::class) override fun fromJson(reader: JsonReader): T? = try { delegate.fromJsonValue(reader.readJsonValue()) } catch (e: Exception) { Timber.w("Wrongful content - could not parse delegate $delegate") defaultValue } @Throws(IOException::class) override fun toJson(writer: JsonWriter, value: T?) { delegate.toJson(writer, value) } } https://gist.github.com/stefanmedack/719fd8d905c6be982e1b4f807cbda3fa

Slide 43

Slide 43 text

Data Classes and Parsing JSON - Stefan Medack (@Zonic03) val moshi = Moshi.Builder() // "... here be adapters# .add(KotlinJsonAdapterFactory()) .build()

Slide 44

Slide 44 text

Data Classes and Parsing JSON - Stefan Medack (@Zonic03) val moshi = Moshi.Builder() // "... here be adapters# .add( DefaultOnDataMismatchAdapter.newFactory( Article::class.java, null ) ) .add(KotlinJsonAdapterFactory()) .build()

Slide 45

Slide 45 text

Data Classes and Parsing JSON - Stefan Medack (@Zonic03) val moshi = Moshi.Builder() // "... here be adapters# .add( DefaultOnDataMismatchAdapter.newFactory( AspectRatio::class.java, AspectRatio.unknown ) ) .add(KotlinJsonAdapterFactory()) .build()

Slide 46

Slide 46 text

Data Classes and Parsing JSON - Stefan Medack (@Zonic03) val moshi = Moshi.Builder() // "... here be adapters# .add( DefaultOnDataMismatchAdapter.newFactory( ArticlePage::class.java, ArticlePage(…) ) ) .add(KotlinJsonAdapterFactory()) .build()

Slide 47

Slide 47 text

Data Classes and Parsing JSON - Stefan Medack (@Zonic03) Let’s test ▸ What is Date ! -> Successful ✅ ▸ Not all mandatory fields are present? ! -> Successful ✅ ▸ filter general parsing errors ▸ more robust parsing ▸ There can be nulls in non-nullable lists? ! -> Crash &

Slide 48

Slide 48 text

Data Classes and Parsing JSON - Stefan Medack (@Zonic03) Let’s test ▸ What is Date ! -> Successful ✅ ▸ Not all mandatory fields are present? ! -> Successful ✅ ▸ filter general parsing errors ▸ more robust parsing ▸ There can be nulls in non-nullable lists?* ! -> Crash & *https://github.com/square/moshi/issues/331

Slide 49

Slide 49 text

Data Classes and Parsing JSON - Stefan Medack (@Zonic03) @JsonClass(generateAdapter = true) data class Article( val id: String, val title: String, val cell_image: Image, val author: User, val video: Video? = null, val subtitle: String = EMPTY_STRING, val content: List = listOf(), val like_count: Int = 0, val comments: List = listOf(), val published_at: Date = Date(0) )

Slide 50

Slide 50 text

Data Classes and Parsing JSON - Stefan Medack (@Zonic03) @JsonClass(generateAdapter = true) data class Article( val id: String, val title: String, val cell_image: Image, val author: User, val video: Video? = null, val subtitle: String = EMPTY_STRING, val content: List = listOf(), val like_count: Int = 0, val comments: List = listOf(), val published_at: Date = Date(0) )

Slide 51

Slide 51 text

Data Classes and Parsing JSON - Stefan Medack (@Zonic03) @JsonClass(generateAdapter = true) data class Article( val id: String, val title: String, val cell_image: Image, val author: User, val video: Video? = null, val subtitle: String = EMPTY_STRING, val content: List = listOf(), val like_count: Int = 0, val comments: List = listOf(), val published_at: Date = Date(0) )

Slide 52

Slide 52 text

Data Classes and Parsing JSON - Stefan Medack (@Zonic03) class FilterNullValuesFromListAdapter private constructor( private val delegate: JsonAdapter> ) : JsonAdapter>() { @Throws(IOException::class) override fun fromJson(reader: JsonReader): List = delegate.fromJsonValue(reader.readJsonValue())?.filterNotNull() ?: listOf() @Throws(IOException::class) override fun toJson(writer: JsonWriter, value: List?) { delegate.toJson(writer, value) } companion object { @JvmStatic fun newFactory(type: Class): JsonAdapter.Factory { return object : JsonAdapter.Factory { override fun create(requestedType: Type,annotations: Set,moshi: Moshi): JsonAdapter<*>? { val listType = Types.newParameterizedType(List::class.java, type) if (listType != requestedType) { return null } val delegate = moshi.nextAdapter>(this, listType, annotations) return FilterNullValuesFromListAdapter(delegate) } } } } } https://gist.github.com/stefanmedack/005971818edbf56e059f121cd4908c76

Slide 53

Slide 53 text

Data Classes and Parsing JSON - Stefan Medack (@Zonic03) class FilterNullValuesFromListAdapter private constructor( private val delegate: JsonAdapter> ) : JsonAdapter>() { @Throws(IOException::class) override fun fromJson(reader: JsonReader): List = delegate.fromJsonValue(reader.readJsonValue()) ?.filterNotNull() ?: listOf() @Throws(IOException::class) override fun toJson(writer: JsonWriter, value: List?) { delegate.toJson(writer, value) } } https://gist.github.com/stefanmedack/005971818edbf56e059f121cd4908c76

Slide 54

Slide 54 text

Data Classes and Parsing JSON - Stefan Medack (@Zonic03) class FilterNullValuesFromListAdapter private constructor( private val delegate: JsonAdapter> ) : JsonAdapter>() { @Throws(IOException::class) override fun fromJson(reader: JsonReader): List = delegate.fromJsonValue(reader.readJsonValue()) ?.filterNotNull() ?: listOf() @Throws(IOException::class) override fun toJson(writer: JsonWriter, value: List?) { delegate.toJson(writer, value) } } https://gist.github.com/stefanmedack/005971818edbf56e059f121cd4908c76

Slide 55

Slide 55 text

Data Classes and Parsing JSON - Stefan Medack (@Zonic03) class FilterNullValuesFromListAdapter private constructor( private val delegate: JsonAdapter> ) : JsonAdapter>() { @Throws(IOException::class) override fun fromJson(reader: JsonReader): List = delegate.fromJsonValue(reader.readJsonValue()) ?.filterNotNull() ?: listOf() @Throws(IOException::class) override fun toJson(writer: JsonWriter, value: List?) { delegate.toJson(writer, value) } } https://gist.github.com/stefanmedack/005971818edbf56e059f121cd4908c76

Slide 56

Slide 56 text

Data Classes and Parsing JSON - Stefan Medack (@Zonic03) class FilterNullValuesFromListAdapter private constructor( private val delegate: JsonAdapter> ) : JsonAdapter>() { @Throws(IOException::class) override fun fromJson(reader: JsonReader): List = delegate.fromJsonValue(reader.readJsonValue()) ?.filterNotNull() ?: listOf() @Throws(IOException::class) override fun toJson(writer: JsonWriter, value: List?) { delegate.toJson(writer, value) } } https://gist.github.com/stefanmedack/005971818edbf56e059f121cd4908c76

Slide 57

Slide 57 text

Data Classes and Parsing JSON - Stefan Medack (@Zonic03) val moshi = Moshi.Builder() // "... here be adapters# .add(KotlinJsonAdapterFactory()) .build()

Slide 58

Slide 58 text

Data Classes and Parsing JSON - Stefan Medack (@Zonic03) val moshi = Moshi.Builder() .add( FilterNullValuesFromListAdapter.newFactory( Content::class.java ) ) .add(KotlinJsonAdapterFactory()) .build()

Slide 59

Slide 59 text

Data Classes and Parsing JSON - Stefan Medack (@Zonic03) val moshi = Moshi.Builder() .add( FilterNullValuesFromListAdapter.newFactory( Comment::class.java ) ) .add(KotlinJsonAdapterFactory()) .build()

Slide 60

Slide 60 text

Data Classes and Parsing JSON - Stefan Medack (@Zonic03) Let’s test ▸ What is Date ! -> Successful ✅ ▸ Not all mandatory fields are present? ! -> Successful ✅ ▸ There is null in the list? ! -> Successful ✅

Slide 61

Slide 61 text

Data Classes and Parsing JSON - Stefan Medack (@Zonic03) TL;FA ▸ Kotlin is fun ▸ Immutable, non-nullable and constructor default values ▸ Parse JSON into Data Classes with Moshi ▸ Filter „broken“ content from bottom up ▸ Workaround for List-Bug in Moshi ▸ maybe not suited for very big set of models

Slide 62

Slide 62 text

Data Classes and Parsing JSON - Stefan Medack (@Zonic03) Links ▸ Blogpost: https://engineering.kitchenstories.io/caf8a599df9e ▸ MoshiKotlinExample: https://github.com/stefanmedack/MoshiKotlinExample ▸ Moshi: https://github.com/square/moshi ▸ Moshi with Kotlin: https://medium.com/square-corner-blog/fcd6ef99256b ▸ Exploring Moshi’s Kotlin Code Gen: https://medium.com/@sweers/dec09d72de5e ▸ why to include moshi-kotlin: https://github.com/square/moshi/issues/315 ▸ discussion on using kotlin-reflect: https://github.com/square/moshi/issues/307 ▸ Moshi Bug nulls in non-nullable lists: https://github.com/square/moshi/issues/331 ▸ Adapter1: https://gist.github.com/stefanmedack/719fd8d905c6be982e1b4f807cbda3fa ▸ Adapter2: https://gist.github.com/stefanmedack/005971818edbf56e059f121cd4908c76

Slide 63

Slide 63 text

We are hiring! Deutsch - kitchenstories.io/de/jobs-de English - kitchenstories.io/en/jobs

Slide 64

Slide 64 text

Thank you for listening!
 
 Questions? Stefan Medack Zonic03 https://engineering.kitchenstories.io/ caf8a599df9e

Slide 65

Slide 65 text

No content

Slide 66

Slide 66 text

No content