GSON, data classes and the absence of constructor invocation


TL;DR  GSON does not invoke your classes constructor (if there's no constructor with zero arguments).

I've had to adapt a network call today, where the reponse class looked something like that:
data class Contract(
        val id: String,
        val branchType: BranchType,
        val description: String
)

The problem was, that the responded BranchType was an enum with more possible values than the enum Branch we use locally for our use-cases (and the additional values can actually be mapped to the enum values we have locally). That's when I thought that this can easily be mapped using an additional field set in the data class:
data class Contract(
        val id: String,
        val branchType: BranchType,
        val description: String
) {
            val branch: Branch? = when (branchType) {
                BranchType.Apple -> Branch.Apple
                BranchType.Banana -> Branch.Banana
                BranchType.Grape -> Branch.Grape
                else -> null
            }
}
Looks good, right? As we use the branch field actually quite often on that object, I wanted to keep the values stored as a field, and not just create a getter-property (using get() =  )

So, running this code, and getting the response from the server and deserializing it with GSON actually just got me only nulls as a branch, while all other fields have been set correctly. Not being able to understand what's going on, I started checking the decompiled Java-equivalent of the bytecode generated from this class (using the IntelliJ built-in  Tools -> Kotlin -> Show Kotlin Bytecode  tool), which seemed to be alright:
public Contract(@Nullable String id, @Nullable BranchType branchType, @Nullable String description) {
  Branch var10;
  label18: {
     super();
     this.id = id;
     this.branchType = branchType;
     this.description = description;
     BranchType var10001 = this.branchType;
     if (this.branchType != null) {
        switch(Contract$WhenMappings.$EnumSwitchMapping${'$'}0[var10001.ordinal()]) {
        case 1:
           var10 = Branch.Apple;
           break label18;
        case 2:
           var10 = Branch.Banana;
           break label18;
        case 3:
           var10 = Branch.Grape;
           break label18;
        }
     }

     var10 = null;
  }

  this.branch = var10;
}
There is - as expected - only one constructor, with all fields as parameters, and after assigning the values to the fields, the branch is mapped by the branchType (the decompiled version of an enum-when-operation actually is does not look very nice). 

As I got more curious, I debugged my when-construct (with debugger and debug prints), and the code was actually never run. This got me really curious, and got me some time searching the web for possible answers. (At this point I already remembered, that GSON does use reflection, but why did the when-mapping actually) never run?

So, eventually, I found the answer - quite extensively explained - on stackoverflow.
GSON, normally, does not invoke any constructor at all. It just creates an object without calling the initializer ⁄ constructor, and then sets the fields using reflection. Only if a no-args-constructor is available, it will be invoked by GSON (but at the time of constructor invocation, the fields are obviously not set yet, so there can be done some work, but nothing which is dependent on fields which are deserialized by GSON.

I then just changed my implementation to a custom getter - with the drawback that it's invoked on every access - but I'm ok with that for now:
val branch: Branch?
    get() = when (branchType) {
        BranchType.Apple -> Branch.Apple
        BranchType.Banana -> Branch.Banana
        BranchType.Grape -> Branch.Grape
        else -> null
    }