gson: Java 14/15 records can not set final field
Hi, as the following error describes, java records (preview feature) deserialization isn’t working in gson, Jackson is adding support in its very soon release 2.12, is there going to be same support/fix in gson ?
java.lang.AssertionError: AssertionError (GSON 2.8.6): java.lang.IllegalAccessException: Can not set final java.lang.String field JsonGsonRecordTest$Emp.name to java.lang.String
Here’s a sample test
record Emp(String name) {}
@Test
void deserializeEngineer() {
Gson j = new GsonBuilder().setPrettyPrinting().create();
var empJson = """
{
"name": "bob"
}""";
var empObj = j.fromJson(empJson, Emp.class);
}
About this issue
- Original URL
- State: closed
- Created 4 years ago
- Reactions: 30
- Comments: 25 (3 by maintainers)
Commits related to this issue
- Change settings classes to records GSON doesn't like it, but there is a workaround at https://github.com/google/gson/issues/1794 — committed to aroelke/mtg-workstation by aroelke 3 years ago
- GuiSettings: suppress ClassCanBeRecord inspection Class GuiSettings cannot be converted into a record at this time due to lack of support for records in Gson library, which is used for serialization ... — committed to rybak/resoday by rybak 2 years ago
- Support Java Records when present in JVM. (#2201) * Support Java Records when present in JVM. Fixes google/gson#1794 Added support in the ReflectionHelper to detect if a class is a record on t... — committed to google/gson by staale 2 years ago
Another workaround would be to use a factory so you don’t have to write deserializers for each record.
The just-released 2.10 includes these changes.
The problem seems to exist only in Java 15 with preview enabled. Running your test under Java 14 with preview enabled and printing the
empObj
givesEmp[name=bob]
.JDKs used,
This is due to changes in Java 15 that makes final fields in records notmodifiable via reflection. More information can be found here:
The relevant part on handling them going forward:
A current workaround is to write a Deserializers that uses the records constructor instead of reflection.
Although gson is in maintenance mode, is this still being considered? Missing the record feature is a hard no-go for some.
Tried with jdk16 and the issue still exists. Is this planned to be supported in Gson in near future?
For what it’s worth, we added in the type adapter mentioned above (https://github.com/Marcono1234/gson-record-type-adapter-factory) and it works OK. That said we are thinking about moving off Gson (probably to Jackson?) long term.
If it helps anyone, this issue does not happen with Jackson
Likely because Gson does not support local classes, see also #1510. The rationale was probably that local classes could capture variables from the enclosing context, which could prevent correct serialization and deserialization. However, for Records this restriction by Gson should not be needed because they are always
static
(and therefore cannot capture anything), even as local classes, see JLS 16 §8.10.Edit: Have created #1969 proposing to support static local classes.
@alexx-dvorkin I’m wondering, whether just calling
reader.skipValue()
wouldn’t be enough when the field isn’t mapped at all?One issue I have noticed is that this workaround doesn’t seem to work with field naming policies or the
@SerializedName
annotation. Making it work with naming policies doesn’t seem a possibility at the moment without somehow transforming aRecordComponent
into aField
.I made some tweaks to the code to make sure it works with the SerializedName annotation, although as I mentioned, I don’t think field naming policies will work at the moment.
It would be great to see some proper support for records in the future. If it’s not a priority at the moment as the technology is new, I would be willing to PR support, and I’m sure others would too.
https://gist.github.com/knightzmc/cf26d9931d32c78c5d777cc719658639
(edit: converted to a gist)
In case someone is interested, I have created the project https://github.com/Marcono1234/gson-record-type-adapter-factory which implements a Record class type adapter factory as standalone library. It includes the features which have been mentioned here in the comments:
@SerializedName
and@JsonAdapter
support, and support for unknown JSON properties and missing values for Record components of primitive types (opt-in features). Feedback is appreciated in case someone wants to try it out 🙂(Note that I am not a maintainer of Gson, but I would also like to see built-in Gson Record support in the future. However, Gson is currently still built for Java 6, so the best choice for now is probably a separate library adding Record support.)
@Sollace, your map is missing
short
.@sceutre, thank you for sharing the
RecordTypeAdapterFactory
. To correctly deal withList<Xxx>
, I’ve adapted your code tocom.google.gson.reflect.TypeToken
:The following Deserializer works fine with JDK16 (workaround)
PS Start the test with “–add-opens YourTestModul/de.sph.gsonrecordtest=com.google.gson”