Why converting Autovalue class to Kotlin data class may not be "pragmatic" for JSON models at this moment

In this post I want to share why I might not convert all my AutoValue classes to Kotlin data classes at this moment.

Don’t get me wrong. I love Kotlin, and I am slowly converting my existing project to Kotlin. However, I found in my particular use case that the data class might not be better than Auto Value class.

Before we jump into the issues directly, let me define the context of my use case first:

  • The value classes represents JSON fetched from backend server. I don’t create the object myself. It is done via retrofit and gson so I don’t need extra builder declarations (which can be cumbersome to maintain too)
  • I need Parcelable implementation.
  • I cannot commit to 100% kotlin.
  • I don’t want to write any extra code (type adapters, parcelable fields) for my value classes.

It is my specific use case, and it might not be the same as yours.

A sample value class in AutoValue:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
@AutoValue
public abstract class JavaPerson implements Parcelable {

    @NonNull
    public abstract String name();

    public abstract int age();

    @NonNull
    public abstract String job();

    @NonNull
    public static TypeAdapter<JavaPerson> typeAdapter(@NonNull Gson gson) {
        return new AutoValue_JavaPerson.GsonTypeAdapter(gson);
    }
}

With the help of AutoValue extensions like auto-value-gson and auto-value-parcel, I get a Person class with Parcelable implemented and a gson TypeAdapter generated along with nice implementations of hashCode(), equals(), and toString(). It’s straightforward enough and I don’t really write code for my value classes.

The equivalent data class in Kotlin

1
data class Person(val name: String, val age: Int, val job: String)

Yes, it’s even shorter and I love it! But wait. It does not have Parcelable and TypeAdapter?

This is the problem I am running into. I have done some research but I have yet to find a good solution. There are some libraries or plugins that address the problem, but they are not a “1:1” replacement for AutoValue.

Parcelable for data class

  1. Parcelable Code Generator - It’s a IntelliJ plugin that generates necessary parcelable fields and methods. It works great, but the generated code lives in your codebase and becomes something you have to maintain. (the same argument that Hadi Hariri gives to IDE plugins for Java pojo)
  2. PaperParcel - A annotation processor available for data classes. It eliminates “some” boilerplate code when implementing Parcelable interface.

(Quote from PaperParcel docs)

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
@PaperParcel
data class User(
    val id: Long,
    val firstName: String,
    val lastName: String
) : Parcelable {
  companion object {
    @JvmField val CREATOR = PaperParcelUser.CREATOR
  }

  override fun describeContents() = 0

  override fun writeToParcel(dest: Parcel, flags: Int) {
    PaperParcelUser.writeToParcel(this, dest, flags)
  }
}

Optional: If you don’t mind a minor amount of reflection, the paperparcel-kotlin module provides PaperParcelable. PaperParcelable is an interface with default implementations written for describeContents and writeToParcel(…) so you don’t have to write them yourself, e.g.:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
@PaperParcel
data class User(
    val id: Long,
    val firstName: String,
    val lastName: String
) : PaperParcelable {
  companion object {
    @JvmField val CREATOR = PaperParcelUser.CREATOR
  }
}

To reduce most code, you need to use reflection and include kotlin-reflect module. Reflection is not that terrible in my opinion, and I use multidex already. However it is something you have to consider when switching from auto value classes.

JSON deserialization for data class

Many developers including me might think gson will just work with kotlin data class, but the result might be different:

1
2
3
4
5
6
7
8
9
fun main(args: Array<String>) {

    val json = """{"name": "Eric", "age": 32, "job": null}"""

    val gsonPerson: Person = Gson().fromJson(json, Person::class.java)
    println("GSON -> $gsonPerson")
}

data class Person(val name: String, val age: Int, val job: String)

When you run it, it compiles and runs, but you get:

1
GSON -> Person(name=Eric, age=32, job=null) // job should be a non-null property

Surprised? I was, but if you look at one [moshi issue], Jake Wharton commented:

Kotlin’s nullability is a facade and it’s very easy to put nulls in a backing field whose property was declared as non-nullable when using reflection, as this library does heavily.

This is actually a request for a @Json(required=true) annotation which will put presence check before returning a class instance to application code. Although does an explicit null in the JSON actually fulfill the requirement for presence? Or does required also imply non-null?

Gson’s default deserialization has the same issue. It uses java reflection to create instances and set the value directly on the backing fields.

So in order to parse JSON into Kotlin data class, you have two options:

  • Write your own TypeAdapters, but this becomes a lot of code and write and maintain
  • Use Moshi or Jackson with their kotlin-module, but again they both use kotlin reflection to access property information. It’s something you do not need when using AutoValue.

Conclusion

Stay with Auto Value (for now) if you are already using it, since with those well developed extension libraries Auto value might serve you better than data classes.

My wish list for data class:

  • An Android platform specific code generation (by Google and Jetbrains) for parcelable fields since data class is a built-in language, not a library we can easily tweak or fork.
  • An annotation processor that generates type adapters for data classes just like auto-value-gson.
  • Some “creative ways” from Square engineers (I believe they can as they always do!)
comments powered by Disqus
Content written by Eric Lin with passions in programming
Built with Hugo
Theme Stack designed by Jimmy