2

Kotlin's val attributes, by design are immutable. They are supposed to be fixed and unchangeable after initialization.

However, I accidentally found out that Gson is able to modify those attributes.

Consider this example:

//Entity class:
class User {
    val name: String = ""
    val age: Int = 0
}

Since the name and age are defined as val and are initialized to empty string and zero, they must remain the same and never change.

However, this test succeeds:

fun main() {
    val json = "{\"name\":\"Mousa\",\"age\":30}"
    val user = Gson().fromJson<User>(json, User::class.java)
    assert(user.name == "Mousa")
    assert(user.age == 30)
    print("Success")
}

Using this feature makes the code cleaner. Because those attributes are modified by Gson and cannot be modified by the rest of our code. Otherwise, I need to provide some mutable backing fields for Gson and some immutable attributes for the rest of the code.

I don't know if this is a "Feature" of Gson or a "Bug". So my question is: can we rely on this, or this behavior might change later?

Mousa
  • 2,190
  • 3
  • 21
  • 34

1 Answers1

5

If you run javap -p User.class, you get the following output:

public final class q54491286.User {
    private final java.lang.String name;
    private final int age;
    public final java.lang.String getName();
    public final int getAge();
    public q54491286.User();
}

Kotlin immutability is backed by no mutator methods generated for fields that are (implicitly) declared private and final. Unless you define a custom type adapter, Gson, by design, uses neither accessors nor mutators during serialization and deserialization respectively by design for plain data object classes (See more here in the Using fields vs getters to indicate Json elements section). Also, Gson can modify final fields, and that's a design choice too.

Due to heavy use of reflection, immutability is pretty a relative thing in Java. For example, I can easily modify an immutable (by design) string using reflection making it a sort of mutable (by intention):

final String s = "foo";
final Field valueField = String.class.getDeclaredField("value");
valueField.setAccessible(true);
valueField.set(s, new char[]{ 'b', 'a', 'r' });
System.out.println(s);

This produces bar when using my java from java version "1.8.0_191". No immutability, he-he.

From the other hand, if your class could be detected by Gson type adapter strategies redefining method in a generic way (Gson prohibits re-defining Object and JsonElement type adapters), you'd be able to write a custom deserializer that prohibits Kotlin-generated classes (detected by the kotlin.Metadata annotation) from having read-only fields being deserialized (not sure if it has a benefit, though). Or just implement a deserializer that will scan a JSON stream/tree for non-read-only fields and use them only while constructing a new User object with its legal constructor not using reflection.

I don't know if this is a "Feature" of Gson or a "Bug".

Summarizing it up, it's a feature.

So my question is: can we rely on this, or this behavior might change later?

This, most likely, will not ever change in Gson 2.x. As far as I know, there are no any plans to release a major update, Gson 3.x and so forth.