2

In Kotlin, how can an instance's read-only val field be mutated?

In the following Kotlin code, gson.fromJson(...) gets a Thing(0) from the InstanceCreator and somehow manages to mutate value from 0 to 1, then return that object. How?

import com.google.gson.GsonBuilder
import com.google.gson.InstanceCreator

class Thing(val value: Int)

fun main() {
    val creator = InstanceCreator { Thing(0) }
    val gson = GsonBuilder().registerTypeAdapter(Thing::class.java, creator).create()
    val thing = gson.fromJson("""{"value":1}""", Thing::class.java)
    println(thing.value) // 1
}

I verified that the object returned by gson.fromJson(...) is the same object provided by the InstanceCreator, so it's not creating a new instance based on the one provided by the InstanceCreator.

I also tried setting value using reflection, but didn't find a way. There was no setter available on the val field.

tashburn
  • 145
  • 6
  • Also note that you can change [`final` fields at runtime using reflection](https://stackoverflow.com/a/3301720/5515060) – Lino Apr 19 '21 at 14:01
  • 1
    It's because GSON is a java reflection based library that doesn't "play nice" with Kotlin - it can initialize non nullable fields as nulls and isn't aware of default values either. For new projects in Kotlin you should consider more modern solutions like kotlinx.serialization or moshi. – Pawel Apr 19 '21 at 17:59
  • Being able to mutate `val`s seems like a benefit for my use case. I'm using an API that's sending me a high rate of JSON structures. I want to deserialize them quickly into objects from an object pool to avoid new allocations and the resulting GC pause. I could make the object fields `var`s, but having them as `val`s enforces read-only elsewhere in the code. – tashburn Apr 20 '21 at 21:54

1 Answers1

3

I also tried setting value using reflection

Well, you can do it if you use Java's reflection API, rather than Kotlin's.

Kotlin val properties translates to a private Java field with only a public getter. You can set the private field using reflection. For example:

val x = Thing(10)
val valueField = x.javaClass.getDeclaredField("value")
valueField.trySetAccessible()
valueField.setInt(x, 20)
println(x.value)

This is probably also what Gson does under the hood.

Sweeper
  • 213,210
  • 22
  • 193
  • 313
  • That works! I wish I could upvote you twice for the miniscule number of seconds that it took you to respond after I posted this question. StackOverflow even says I can't "accept an answer" for another 2 minutes. – tashburn Apr 19 '21 at 14:04