2011-09-11 09:04:56 +02:00
|
|
|
/*
|
|
|
|
* Copyright (C) 2011 Google Inc.
|
|
|
|
*
|
|
|
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
|
|
* you may not use this file except in compliance with the License.
|
|
|
|
* You may obtain a copy of the License at
|
|
|
|
*
|
|
|
|
* http://www.apache.org/licenses/LICENSE-2.0
|
|
|
|
*
|
|
|
|
* Unless required by applicable law or agreed to in writing, software
|
|
|
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
|
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
|
|
* See the License for the specific language governing permissions and
|
|
|
|
* limitations under the License.
|
|
|
|
*/
|
|
|
|
|
|
|
|
package com.google.gson.internal.bind;
|
|
|
|
|
2011-11-21 06:42:30 +01:00
|
|
|
import com.google.gson.FieldNamingStrategy;
|
2011-11-20 16:23:08 +01:00
|
|
|
import com.google.gson.Gson;
|
2022-04-17 18:05:18 +02:00
|
|
|
import com.google.gson.JsonIOException;
|
2022-10-22 18:01:56 +02:00
|
|
|
import com.google.gson.JsonParseException;
|
2011-09-11 09:04:56 +02:00
|
|
|
import com.google.gson.JsonSyntaxException;
|
2022-04-17 18:05:18 +02:00
|
|
|
import com.google.gson.ReflectionAccessFilter;
|
|
|
|
import com.google.gson.ReflectionAccessFilter.FilterResult;
|
2011-11-20 16:23:08 +01:00
|
|
|
import com.google.gson.TypeAdapter;
|
2011-12-23 19:27:13 +01:00
|
|
|
import com.google.gson.TypeAdapterFactory;
|
2014-03-26 18:59:54 +01:00
|
|
|
import com.google.gson.annotations.JsonAdapter;
|
2011-11-21 06:42:30 +01:00
|
|
|
import com.google.gson.annotations.SerializedName;
|
2011-09-11 09:04:56 +02:00
|
|
|
import com.google.gson.internal.$Gson$Types;
|
2011-09-16 06:55:52 +02:00
|
|
|
import com.google.gson.internal.ConstructorConstructor;
|
2011-11-22 08:37:13 +01:00
|
|
|
import com.google.gson.internal.Excluder;
|
2011-09-11 09:04:56 +02:00
|
|
|
import com.google.gson.internal.ObjectConstructor;
|
|
|
|
import com.google.gson.internal.Primitives;
|
2022-04-17 18:05:18 +02:00
|
|
|
import com.google.gson.internal.ReflectionAccessFilterHelper;
|
2023-04-15 22:36:26 +02:00
|
|
|
import com.google.gson.internal.TroubleshootingGuide;
|
2021-11-09 16:16:35 +01:00
|
|
|
import com.google.gson.internal.reflect.ReflectionHelper;
|
2011-09-11 09:04:56 +02:00
|
|
|
import com.google.gson.reflect.TypeToken;
|
|
|
|
import com.google.gson.stream.JsonReader;
|
|
|
|
import com.google.gson.stream.JsonToken;
|
|
|
|
import com.google.gson.stream.JsonWriter;
|
2015-12-27 07:39:19 +01:00
|
|
|
import java.io.IOException;
|
2022-10-22 18:01:56 +02:00
|
|
|
import java.lang.reflect.AccessibleObject;
|
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 the JVM (via reflection), and if so, we will create a special
RecordAdapter to deserialize records, using the canoncial constructor.
The ReflectionTypeAdapterFactory had to be refactored a bit to support
this. The Adapter class inside the factory is now abstract, with
concrete implementations for normal field reflection and for Records.
The common code is in the Adapter, with each implementation
deserializing values into an intermediary object.
For the FieldReflectionAdapter, the intermediary is actually the final
result, and field access is used to write to fields as before. For the
RecordAdapter the intermediary is the Object[] to pass to the Record
constructor.
* Fixed comments from @Marcono1234
Also updated so that we now use the record accessor method to read out
values from a record, so that direct field access is not necessary.
Also added some tests, that should only execute on Java versions with
record support, and be ignored for other JVMs
* Fixed additional comments from @Marcono1234
* Made Adapter in ReflectiveTypeAdapterFactory public
Fix comment from @eamonnmcmanus
2022-10-11 18:13:49 +02:00
|
|
|
import java.lang.reflect.Constructor;
|
2015-12-27 07:39:19 +01:00
|
|
|
import java.lang.reflect.Field;
|
2022-10-22 18:01:56 +02:00
|
|
|
import java.lang.reflect.InvocationTargetException;
|
|
|
|
import java.lang.reflect.Member;
|
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 the JVM (via reflection), and if so, we will create a special
RecordAdapter to deserialize records, using the canoncial constructor.
The ReflectionTypeAdapterFactory had to be refactored a bit to support
this. The Adapter class inside the factory is now abstract, with
concrete implementations for normal field reflection and for Records.
The common code is in the Adapter, with each implementation
deserializing values into an intermediary object.
For the FieldReflectionAdapter, the intermediary is actually the final
result, and field access is used to write to fields as before. For the
RecordAdapter the intermediary is the Object[] to pass to the Record
constructor.
* Fixed comments from @Marcono1234
Also updated so that we now use the record accessor method to read out
values from a record, so that direct field access is not necessary.
Also added some tests, that should only execute on Java versions with
record support, and be ignored for other JVMs
* Fixed additional comments from @Marcono1234
* Made Adapter in ReflectiveTypeAdapterFactory public
Fix comment from @eamonnmcmanus
2022-10-11 18:13:49 +02:00
|
|
|
import java.lang.reflect.Method;
|
2022-04-17 18:05:18 +02:00
|
|
|
import java.lang.reflect.Modifier;
|
2015-12-27 07:39:19 +01:00
|
|
|
import java.lang.reflect.Type;
|
2016-04-27 06:28:51 +02:00
|
|
|
import java.util.ArrayList;
|
2022-10-22 18:01:56 +02:00
|
|
|
import java.util.Arrays;
|
2016-04-27 06:28:51 +02:00
|
|
|
import java.util.Collections;
|
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 the JVM (via reflection), and if so, we will create a special
RecordAdapter to deserialize records, using the canoncial constructor.
The ReflectionTypeAdapterFactory had to be refactored a bit to support
this. The Adapter class inside the factory is now abstract, with
concrete implementations for normal field reflection and for Records.
The common code is in the Adapter, with each implementation
deserializing values into an intermediary object.
For the FieldReflectionAdapter, the intermediary is actually the final
result, and field access is used to write to fields as before. For the
RecordAdapter the intermediary is the Object[] to pass to the Record
constructor.
* Fixed comments from @Marcono1234
Also updated so that we now use the record accessor method to read out
values from a record, so that direct field access is not necessary.
Also added some tests, that should only execute on Java versions with
record support, and be ignored for other JVMs
* Fixed additional comments from @Marcono1234
* Made Adapter in ReflectiveTypeAdapterFactory public
Fix comment from @eamonnmcmanus
2022-10-11 18:13:49 +02:00
|
|
|
import java.util.HashMap;
|
2022-10-22 18:01:56 +02:00
|
|
|
import java.util.LinkedHashMap;
|
2015-12-27 07:39:19 +01:00
|
|
|
import java.util.List;
|
|
|
|
import java.util.Map;
|
|
|
|
|
2011-09-11 09:04:56 +02:00
|
|
|
/** Type adapter that reflects over the fields and methods of a class. */
|
2011-12-23 19:27:13 +01:00
|
|
|
public final class ReflectiveTypeAdapterFactory implements TypeAdapterFactory {
|
2011-09-11 09:04:56 +02:00
|
|
|
private final ConstructorConstructor constructorConstructor;
|
2011-11-21 06:42:30 +01:00
|
|
|
private final FieldNamingStrategy fieldNamingPolicy;
|
2011-11-22 08:37:13 +01:00
|
|
|
private final Excluder excluder;
|
2016-05-19 03:21:39 +02:00
|
|
|
private final JsonAdapterAnnotationTypeAdapterFactory jsonAdapterFactory;
|
2022-04-17 18:05:18 +02:00
|
|
|
private final List<ReflectionAccessFilter> reflectionFilters;
|
2011-09-11 09:04:56 +02:00
|
|
|
|
2011-11-21 06:42:30 +01:00
|
|
|
public ReflectiveTypeAdapterFactory(
|
|
|
|
ConstructorConstructor constructorConstructor,
|
2016-05-19 03:21:39 +02:00
|
|
|
FieldNamingStrategy fieldNamingPolicy,
|
|
|
|
Excluder excluder,
|
2022-04-17 18:05:18 +02:00
|
|
|
JsonAdapterAnnotationTypeAdapterFactory jsonAdapterFactory,
|
|
|
|
List<ReflectionAccessFilter> reflectionFilters) {
|
2011-09-11 09:04:56 +02:00
|
|
|
this.constructorConstructor = constructorConstructor;
|
2011-11-21 06:42:30 +01:00
|
|
|
this.fieldNamingPolicy = fieldNamingPolicy;
|
2011-11-22 08:37:13 +01:00
|
|
|
this.excluder = excluder;
|
2016-05-19 03:21:39 +02:00
|
|
|
this.jsonAdapterFactory = jsonAdapterFactory;
|
2022-04-17 18:05:18 +02:00
|
|
|
this.reflectionFilters = reflectionFilters;
|
2011-09-11 09:04:56 +02:00
|
|
|
}
|
|
|
|
|
2022-07-21 20:53:52 +02:00
|
|
|
private boolean includeField(Field f, boolean serialize) {
|
2024-01-29 17:21:04 +01:00
|
|
|
return !excluder.excludeField(f, serialize);
|
2011-09-11 09:04:56 +02:00
|
|
|
}
|
|
|
|
|
2015-10-03 11:01:16 +02:00
|
|
|
/** first element holds the default name */
|
2023-03-01 23:23:27 +01:00
|
|
|
@SuppressWarnings("MixedMutabilityReturnType")
|
2015-10-03 11:01:16 +02:00
|
|
|
private List<String> getFieldNames(Field f) {
|
2016-04-27 06:28:51 +02:00
|
|
|
SerializedName annotation = f.getAnnotation(SerializedName.class);
|
|
|
|
if (annotation == null) {
|
|
|
|
String name = fieldNamingPolicy.translateName(f);
|
|
|
|
return Collections.singletonList(name);
|
|
|
|
}
|
2014-11-16 23:25:23 +01:00
|
|
|
|
2016-04-27 06:28:51 +02:00
|
|
|
String serializedName = annotation.value();
|
|
|
|
String[] alternates = annotation.alternate();
|
|
|
|
if (alternates.length == 0) {
|
|
|
|
return Collections.singletonList(serializedName);
|
|
|
|
}
|
|
|
|
|
2022-04-18 00:27:21 +02:00
|
|
|
List<String> fieldNames = new ArrayList<>(alternates.length + 1);
|
2016-04-27 06:28:51 +02:00
|
|
|
fieldNames.add(serializedName);
|
2022-10-12 23:24:36 +02:00
|
|
|
Collections.addAll(fieldNames, alternates);
|
2015-10-03 11:01:16 +02:00
|
|
|
return fieldNames;
|
2011-09-11 09:04:56 +02:00
|
|
|
}
|
|
|
|
|
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 the JVM (via reflection), and if so, we will create a special
RecordAdapter to deserialize records, using the canoncial constructor.
The ReflectionTypeAdapterFactory had to be refactored a bit to support
this. The Adapter class inside the factory is now abstract, with
concrete implementations for normal field reflection and for Records.
The common code is in the Adapter, with each implementation
deserializing values into an intermediary object.
For the FieldReflectionAdapter, the intermediary is actually the final
result, and field access is used to write to fields as before. For the
RecordAdapter the intermediary is the Object[] to pass to the Record
constructor.
* Fixed comments from @Marcono1234
Also updated so that we now use the record accessor method to read out
values from a record, so that direct field access is not necessary.
Also added some tests, that should only execute on Java versions with
record support, and be ignored for other JVMs
* Fixed additional comments from @Marcono1234
* Made Adapter in ReflectiveTypeAdapterFactory public
Fix comment from @eamonnmcmanus
2022-10-11 18:13:49 +02:00
|
|
|
@Override
|
|
|
|
public <T> TypeAdapter<T> create(Gson gson, final TypeToken<T> type) {
|
2011-09-11 09:04:56 +02:00
|
|
|
Class<? super T> raw = type.getRawType();
|
|
|
|
|
|
|
|
if (!Object.class.isAssignableFrom(raw)) {
|
|
|
|
return null; // it's a primitive!
|
|
|
|
}
|
|
|
|
|
2024-01-29 17:21:04 +01:00
|
|
|
// Don't allow using reflection on anonymous and local classes because synthetic fields for
|
|
|
|
// captured enclosing values make this unreliable
|
|
|
|
if (ReflectionHelper.isAnonymousOrNonStaticLocal(raw)) {
|
|
|
|
// This adapter just serializes and deserializes null, ignoring the actual values
|
|
|
|
// This is done for backward compatibility; troubleshooting-wise it might be better to throw
|
|
|
|
// exceptions
|
|
|
|
return new TypeAdapter<T>() {
|
|
|
|
@Override
|
|
|
|
public T read(JsonReader in) throws IOException {
|
|
|
|
in.skipValue();
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
public void write(JsonWriter out, T value) throws IOException {
|
|
|
|
out.nullValue();
|
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
public String toString() {
|
|
|
|
return "AnonymousOrNonStaticLocalClassAdapter";
|
|
|
|
}
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
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 the JVM (via reflection), and if so, we will create a special
RecordAdapter to deserialize records, using the canoncial constructor.
The ReflectionTypeAdapterFactory had to be refactored a bit to support
this. The Adapter class inside the factory is now abstract, with
concrete implementations for normal field reflection and for Records.
The common code is in the Adapter, with each implementation
deserializing values into an intermediary object.
For the FieldReflectionAdapter, the intermediary is actually the final
result, and field access is used to write to fields as before. For the
RecordAdapter the intermediary is the Object[] to pass to the Record
constructor.
* Fixed comments from @Marcono1234
Also updated so that we now use the record accessor method to read out
values from a record, so that direct field access is not necessary.
Also added some tests, that should only execute on Java versions with
record support, and be ignored for other JVMs
* Fixed additional comments from @Marcono1234
* Made Adapter in ReflectiveTypeAdapterFactory public
Fix comment from @eamonnmcmanus
2022-10-11 18:13:49 +02:00
|
|
|
FilterResult filterResult =
|
|
|
|
ReflectionAccessFilterHelper.getFilterResult(reflectionFilters, raw);
|
2022-04-17 18:05:18 +02:00
|
|
|
if (filterResult == FilterResult.BLOCK_ALL) {
|
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 the JVM (via reflection), and if so, we will create a special
RecordAdapter to deserialize records, using the canoncial constructor.
The ReflectionTypeAdapterFactory had to be refactored a bit to support
this. The Adapter class inside the factory is now abstract, with
concrete implementations for normal field reflection and for Records.
The common code is in the Adapter, with each implementation
deserializing values into an intermediary object.
For the FieldReflectionAdapter, the intermediary is actually the final
result, and field access is used to write to fields as before. For the
RecordAdapter the intermediary is the Object[] to pass to the Record
constructor.
* Fixed comments from @Marcono1234
Also updated so that we now use the record accessor method to read out
values from a record, so that direct field access is not necessary.
Also added some tests, that should only execute on Java versions with
record support, and be ignored for other JVMs
* Fixed additional comments from @Marcono1234
* Made Adapter in ReflectiveTypeAdapterFactory public
Fix comment from @eamonnmcmanus
2022-10-11 18:13:49 +02:00
|
|
|
throw new JsonIOException(
|
2022-10-22 18:01:56 +02:00
|
|
|
"ReflectionAccessFilter does not permit using reflection for "
|
|
|
|
+ raw
|
2023-04-15 22:36:26 +02:00
|
|
|
+ ". Register a TypeAdapter for this type or adjust the access filter.");
|
2022-04-17 18:05:18 +02:00
|
|
|
}
|
|
|
|
boolean blockInaccessible = filterResult == FilterResult.BLOCK_INACCESSIBLE;
|
|
|
|
|
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 the JVM (via reflection), and if so, we will create a special
RecordAdapter to deserialize records, using the canoncial constructor.
The ReflectionTypeAdapterFactory had to be refactored a bit to support
this. The Adapter class inside the factory is now abstract, with
concrete implementations for normal field reflection and for Records.
The common code is in the Adapter, with each implementation
deserializing values into an intermediary object.
For the FieldReflectionAdapter, the intermediary is actually the final
result, and field access is used to write to fields as before. For the
RecordAdapter the intermediary is the Object[] to pass to the Record
constructor.
* Fixed comments from @Marcono1234
Also updated so that we now use the record accessor method to read out
values from a record, so that direct field access is not necessary.
Also added some tests, that should only execute on Java versions with
record support, and be ignored for other JVMs
* Fixed additional comments from @Marcono1234
* Made Adapter in ReflectiveTypeAdapterFactory public
Fix comment from @eamonnmcmanus
2022-10-11 18:13:49 +02:00
|
|
|
// If the type is actually a Java Record, we need to use the RecordAdapter instead. This will
|
|
|
|
// always be false on JVMs that do not support records.
|
|
|
|
if (ReflectionHelper.isRecord(raw)) {
|
2022-10-22 18:01:56 +02:00
|
|
|
@SuppressWarnings("unchecked")
|
|
|
|
TypeAdapter<T> adapter =
|
|
|
|
(TypeAdapter<T>)
|
|
|
|
new RecordAdapter<>(
|
|
|
|
raw, getBoundFields(gson, type, raw, blockInaccessible, true), blockInaccessible);
|
|
|
|
return adapter;
|
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 the JVM (via reflection), and if so, we will create a special
RecordAdapter to deserialize records, using the canoncial constructor.
The ReflectionTypeAdapterFactory had to be refactored a bit to support
this. The Adapter class inside the factory is now abstract, with
concrete implementations for normal field reflection and for Records.
The common code is in the Adapter, with each implementation
deserializing values into an intermediary object.
For the FieldReflectionAdapter, the intermediary is actually the final
result, and field access is used to write to fields as before. For the
RecordAdapter the intermediary is the Object[] to pass to the Record
constructor.
* Fixed comments from @Marcono1234
Also updated so that we now use the record accessor method to read out
values from a record, so that direct field access is not necessary.
Also added some tests, that should only execute on Java versions with
record support, and be ignored for other JVMs
* Fixed additional comments from @Marcono1234
* Made Adapter in ReflectiveTypeAdapterFactory public
Fix comment from @eamonnmcmanus
2022-10-11 18:13:49 +02:00
|
|
|
}
|
|
|
|
|
2012-01-01 14:42:44 +01:00
|
|
|
ObjectConstructor<T> constructor = constructorConstructor.get(type);
|
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 the JVM (via reflection), and if so, we will create a special
RecordAdapter to deserialize records, using the canoncial constructor.
The ReflectionTypeAdapterFactory had to be refactored a bit to support
this. The Adapter class inside the factory is now abstract, with
concrete implementations for normal field reflection and for Records.
The common code is in the Adapter, with each implementation
deserializing values into an intermediary object.
For the FieldReflectionAdapter, the intermediary is actually the final
result, and field access is used to write to fields as before. For the
RecordAdapter the intermediary is the Object[] to pass to the Record
constructor.
* Fixed comments from @Marcono1234
Also updated so that we now use the record accessor method to read out
values from a record, so that direct field access is not necessary.
Also added some tests, that should only execute on Java versions with
record support, and be ignored for other JVMs
* Fixed additional comments from @Marcono1234
* Made Adapter in ReflectiveTypeAdapterFactory public
Fix comment from @eamonnmcmanus
2022-10-11 18:13:49 +02:00
|
|
|
return new FieldReflectionAdapter<>(
|
|
|
|
constructor, getBoundFields(gson, type, raw, blockInaccessible, false));
|
2022-04-17 18:05:18 +02:00
|
|
|
}
|
|
|
|
|
2022-10-22 18:01:56 +02:00
|
|
|
private static <M extends AccessibleObject & Member> void checkAccessible(
|
|
|
|
Object object, M member) {
|
|
|
|
if (!ReflectionAccessFilterHelper.canAccess(
|
|
|
|
member, Modifier.isStatic(member.getModifiers()) ? null : object)) {
|
|
|
|
String memberDescription = ReflectionHelper.getAccessibleObjectDescription(member, true);
|
|
|
|
throw new JsonIOException(
|
|
|
|
memberDescription
|
|
|
|
+ " is not accessible and ReflectionAccessFilter does not permit making it"
|
|
|
|
+ " accessible. Register a TypeAdapter for the declaring type, adjust the access"
|
|
|
|
+ " filter or increase the visibility of the element and its declaring type.");
|
2022-04-17 18:05:18 +02:00
|
|
|
}
|
2011-09-11 09:04:56 +02:00
|
|
|
}
|
|
|
|
|
2022-12-05 02:27:43 +01:00
|
|
|
private BoundField createBoundField(
|
2023-09-30 22:23:31 +02:00
|
|
|
final Gson context,
|
|
|
|
final Field field,
|
|
|
|
final Method accessor,
|
|
|
|
final String serializedName,
|
|
|
|
final TypeToken<?> fieldType,
|
|
|
|
final boolean serialize,
|
|
|
|
final boolean blockInaccessible) {
|
2022-10-22 18:01:56 +02:00
|
|
|
|
2011-09-11 09:04:56 +02:00
|
|
|
final boolean isPrimitive = Primitives.isPrimitive(fieldType.getRawType());
|
2022-10-22 18:01:56 +02:00
|
|
|
|
|
|
|
int modifiers = field.getModifiers();
|
|
|
|
final boolean isStaticFinalField = Modifier.isStatic(modifiers) && Modifier.isFinal(modifiers);
|
|
|
|
|
2016-05-17 22:30:59 +02:00
|
|
|
JsonAdapter annotation = field.getAnnotation(JsonAdapter.class);
|
|
|
|
TypeAdapter<?> mapped = null;
|
|
|
|
if (annotation != null) {
|
2022-08-27 02:36:18 +02:00
|
|
|
// This is not safe; requires that user has specified correct adapter class for @JsonAdapter
|
2016-05-19 03:21:39 +02:00
|
|
|
mapped =
|
|
|
|
jsonAdapterFactory.getTypeAdapter(
|
2023-08-23 02:15:18 +02:00
|
|
|
constructorConstructor, context, fieldType, annotation, false);
|
2016-05-17 22:30:59 +02:00
|
|
|
}
|
|
|
|
final boolean jsonAdapterPresent = mapped != null;
|
2024-01-09 19:19:09 +01:00
|
|
|
if (mapped == null) {
|
|
|
|
mapped = context.getAdapter(fieldType);
|
|
|
|
}
|
2016-05-17 22:30:59 +02:00
|
|
|
|
2022-08-27 02:36:18 +02:00
|
|
|
@SuppressWarnings("unchecked")
|
|
|
|
final TypeAdapter<Object> typeAdapter = (TypeAdapter<Object>) mapped;
|
2023-02-28 23:46:05 +01:00
|
|
|
final TypeAdapter<Object> writeTypeAdapter;
|
|
|
|
if (serialize) {
|
|
|
|
writeTypeAdapter =
|
|
|
|
jsonAdapterPresent
|
|
|
|
? typeAdapter
|
|
|
|
: new TypeAdapterRuntimeTypeWrapper<>(context, typeAdapter, fieldType.getType());
|
|
|
|
} else {
|
|
|
|
// Will never actually be used, but we set it to avoid confusing nullness-analysis tools
|
|
|
|
writeTypeAdapter = typeAdapter;
|
|
|
|
}
|
2023-09-30 22:23:31 +02:00
|
|
|
return new BoundField(serializedName, field) {
|
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 the JVM (via reflection), and if so, we will create a special
RecordAdapter to deserialize records, using the canoncial constructor.
The ReflectionTypeAdapterFactory had to be refactored a bit to support
this. The Adapter class inside the factory is now abstract, with
concrete implementations for normal field reflection and for Records.
The common code is in the Adapter, with each implementation
deserializing values into an intermediary object.
For the FieldReflectionAdapter, the intermediary is actually the final
result, and field access is used to write to fields as before. For the
RecordAdapter the intermediary is the Object[] to pass to the Record
constructor.
* Fixed comments from @Marcono1234
Also updated so that we now use the record accessor method to read out
values from a record, so that direct field access is not necessary.
Also added some tests, that should only execute on Java versions with
record support, and be ignored for other JVMs
* Fixed additional comments from @Marcono1234
* Made Adapter in ReflectiveTypeAdapterFactory public
Fix comment from @eamonnmcmanus
2022-10-11 18:13:49 +02:00
|
|
|
@Override
|
2022-10-22 18:01:56 +02:00
|
|
|
void write(JsonWriter writer, Object source) throws IOException, IllegalAccessException {
|
|
|
|
if (blockInaccessible) {
|
|
|
|
if (accessor == null) {
|
|
|
|
checkAccessible(source, field);
|
|
|
|
} else {
|
|
|
|
// Note: This check might actually be redundant because access check for canonical
|
|
|
|
// constructor should have failed already
|
|
|
|
checkAccessible(source, accessor);
|
|
|
|
}
|
2022-04-17 18:05:18 +02:00
|
|
|
}
|
|
|
|
|
2022-10-22 18:01:56 +02:00
|
|
|
Object fieldValue;
|
|
|
|
if (accessor != null) {
|
|
|
|
try {
|
|
|
|
fieldValue = accessor.invoke(source);
|
|
|
|
} catch (InvocationTargetException e) {
|
|
|
|
String accessorDescription =
|
|
|
|
ReflectionHelper.getAccessibleObjectDescription(accessor, false);
|
|
|
|
throw new JsonIOException(
|
|
|
|
"Accessor " + accessorDescription + " threw exception", e.getCause());
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
fieldValue = field.get(source);
|
|
|
|
}
|
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 the JVM (via reflection), and if so, we will create a special
RecordAdapter to deserialize records, using the canoncial constructor.
The ReflectionTypeAdapterFactory had to be refactored a bit to support
this. The Adapter class inside the factory is now abstract, with
concrete implementations for normal field reflection and for Records.
The common code is in the Adapter, with each implementation
deserializing values into an intermediary object.
For the FieldReflectionAdapter, the intermediary is actually the final
result, and field access is used to write to fields as before. For the
RecordAdapter the intermediary is the Object[] to pass to the Record
constructor.
* Fixed comments from @Marcono1234
Also updated so that we now use the record accessor method to read out
values from a record, so that direct field access is not necessary.
Also added some tests, that should only execute on Java versions with
record support, and be ignored for other JVMs
* Fixed additional comments from @Marcono1234
* Made Adapter in ReflectiveTypeAdapterFactory public
Fix comment from @eamonnmcmanus
2022-10-11 18:13:49 +02:00
|
|
|
if (fieldValue == source) {
|
2022-04-17 18:05:18 +02:00
|
|
|
// avoid direct recursion
|
|
|
|
return;
|
|
|
|
}
|
2023-09-30 22:23:31 +02:00
|
|
|
writer.name(serializedName);
|
2023-02-28 23:46:05 +01:00
|
|
|
writeTypeAdapter.write(writer, fieldValue);
|
2011-09-11 09:04:56 +02:00
|
|
|
}
|
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 the JVM (via reflection), and if so, we will create a special
RecordAdapter to deserialize records, using the canoncial constructor.
The ReflectionTypeAdapterFactory had to be refactored a bit to support
this. The Adapter class inside the factory is now abstract, with
concrete implementations for normal field reflection and for Records.
The common code is in the Adapter, with each implementation
deserializing values into an intermediary object.
For the FieldReflectionAdapter, the intermediary is actually the final
result, and field access is used to write to fields as before. For the
RecordAdapter the intermediary is the Object[] to pass to the Record
constructor.
* Fixed comments from @Marcono1234
Also updated so that we now use the record accessor method to read out
values from a record, so that direct field access is not necessary.
Also added some tests, that should only execute on Java versions with
record support, and be ignored for other JVMs
* Fixed additional comments from @Marcono1234
* Made Adapter in ReflectiveTypeAdapterFactory public
Fix comment from @eamonnmcmanus
2022-10-11 18:13:49 +02:00
|
|
|
|
|
|
|
@Override
|
2022-10-22 18:01:56 +02:00
|
|
|
void readIntoArray(JsonReader reader, int index, Object[] target)
|
|
|
|
throws IOException, JsonParseException {
|
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 the JVM (via reflection), and if so, we will create a special
RecordAdapter to deserialize records, using the canoncial constructor.
The ReflectionTypeAdapterFactory had to be refactored a bit to support
this. The Adapter class inside the factory is now abstract, with
concrete implementations for normal field reflection and for Records.
The common code is in the Adapter, with each implementation
deserializing values into an intermediary object.
For the FieldReflectionAdapter, the intermediary is actually the final
result, and field access is used to write to fields as before. For the
RecordAdapter the intermediary is the Object[] to pass to the Record
constructor.
* Fixed comments from @Marcono1234
Also updated so that we now use the record accessor method to read out
values from a record, so that direct field access is not necessary.
Also added some tests, that should only execute on Java versions with
record support, and be ignored for other JVMs
* Fixed additional comments from @Marcono1234
* Made Adapter in ReflectiveTypeAdapterFactory public
Fix comment from @eamonnmcmanus
2022-10-11 18:13:49 +02:00
|
|
|
Object fieldValue = typeAdapter.read(reader);
|
2022-10-22 18:01:56 +02:00
|
|
|
if (fieldValue == null && isPrimitive) {
|
|
|
|
throw new JsonParseException(
|
|
|
|
"null is not allowed as value for record component '"
|
|
|
|
+ fieldName
|
2023-11-15 00:09:54 +01:00
|
|
|
+ "' of primitive type; at path "
|
2022-10-22 18:01:56 +02:00
|
|
|
+ reader.getPath());
|
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 the JVM (via reflection), and if so, we will create a special
RecordAdapter to deserialize records, using the canoncial constructor.
The ReflectionTypeAdapterFactory had to be refactored a bit to support
this. The Adapter class inside the factory is now abstract, with
concrete implementations for normal field reflection and for Records.
The common code is in the Adapter, with each implementation
deserializing values into an intermediary object.
For the FieldReflectionAdapter, the intermediary is actually the final
result, and field access is used to write to fields as before. For the
RecordAdapter the intermediary is the Object[] to pass to the Record
constructor.
* Fixed comments from @Marcono1234
Also updated so that we now use the record accessor method to read out
values from a record, so that direct field access is not necessary.
Also added some tests, that should only execute on Java versions with
record support, and be ignored for other JVMs
* Fixed additional comments from @Marcono1234
* Made Adapter in ReflectiveTypeAdapterFactory public
Fix comment from @eamonnmcmanus
2022-10-11 18:13:49 +02:00
|
|
|
}
|
2022-10-22 18:01:56 +02:00
|
|
|
target[index] = fieldValue;
|
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 the JVM (via reflection), and if so, we will create a special
RecordAdapter to deserialize records, using the canoncial constructor.
The ReflectionTypeAdapterFactory had to be refactored a bit to support
this. The Adapter class inside the factory is now abstract, with
concrete implementations for normal field reflection and for Records.
The common code is in the Adapter, with each implementation
deserializing values into an intermediary object.
For the FieldReflectionAdapter, the intermediary is actually the final
result, and field access is used to write to fields as before. For the
RecordAdapter the intermediary is the Object[] to pass to the Record
constructor.
* Fixed comments from @Marcono1234
Also updated so that we now use the record accessor method to read out
values from a record, so that direct field access is not necessary.
Also added some tests, that should only execute on Java versions with
record support, and be ignored for other JVMs
* Fixed additional comments from @Marcono1234
* Made Adapter in ReflectiveTypeAdapterFactory public
Fix comment from @eamonnmcmanus
2022-10-11 18:13:49 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
void readIntoField(JsonReader reader, Object target)
|
2011-09-11 09:04:56 +02:00
|
|
|
throws IOException, IllegalAccessException {
|
|
|
|
Object fieldValue = typeAdapter.read(reader);
|
|
|
|
if (fieldValue != null || !isPrimitive) {
|
2022-04-17 18:05:18 +02:00
|
|
|
if (blockInaccessible) {
|
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 the JVM (via reflection), and if so, we will create a special
RecordAdapter to deserialize records, using the canoncial constructor.
The ReflectionTypeAdapterFactory had to be refactored a bit to support
this. The Adapter class inside the factory is now abstract, with
concrete implementations for normal field reflection and for Records.
The common code is in the Adapter, with each implementation
deserializing values into an intermediary object.
For the FieldReflectionAdapter, the intermediary is actually the final
result, and field access is used to write to fields as before. For the
RecordAdapter the intermediary is the Object[] to pass to the Record
constructor.
* Fixed comments from @Marcono1234
Also updated so that we now use the record accessor method to read out
values from a record, so that direct field access is not necessary.
Also added some tests, that should only execute on Java versions with
record support, and be ignored for other JVMs
* Fixed additional comments from @Marcono1234
* Made Adapter in ReflectiveTypeAdapterFactory public
Fix comment from @eamonnmcmanus
2022-10-11 18:13:49 +02:00
|
|
|
checkAccessible(target, field);
|
2022-10-22 18:01:56 +02:00
|
|
|
} else if (isStaticFinalField) {
|
|
|
|
// Reflection does not permit setting value of `static final` field, even after calling
|
2023-11-15 00:09:54 +01:00
|
|
|
// `setAccessible`
|
|
|
|
// Handle this here to avoid causing IllegalAccessException when calling `Field.set`
|
2022-10-22 18:01:56 +02:00
|
|
|
String fieldDescription = ReflectionHelper.getAccessibleObjectDescription(field, false);
|
|
|
|
throw new JsonIOException("Cannot set value of 'static final' " + fieldDescription);
|
2022-04-17 18:05:18 +02:00
|
|
|
}
|
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 the JVM (via reflection), and if so, we will create a special
RecordAdapter to deserialize records, using the canoncial constructor.
The ReflectionTypeAdapterFactory had to be refactored a bit to support
this. The Adapter class inside the factory is now abstract, with
concrete implementations for normal field reflection and for Records.
The common code is in the Adapter, with each implementation
deserializing values into an intermediary object.
For the FieldReflectionAdapter, the intermediary is actually the final
result, and field access is used to write to fields as before. For the
RecordAdapter the intermediary is the Object[] to pass to the Record
constructor.
* Fixed comments from @Marcono1234
Also updated so that we now use the record accessor method to read out
values from a record, so that direct field access is not necessary.
Also added some tests, that should only execute on Java versions with
record support, and be ignored for other JVMs
* Fixed additional comments from @Marcono1234
* Made Adapter in ReflectiveTypeAdapterFactory public
Fix comment from @eamonnmcmanus
2022-10-11 18:13:49 +02:00
|
|
|
field.set(target, fieldValue);
|
2011-09-11 09:04:56 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
2023-09-30 22:23:31 +02:00
|
|
|
private static class FieldsData {
|
|
|
|
public static final FieldsData EMPTY =
|
|
|
|
new FieldsData(
|
|
|
|
Collections.<String, BoundField>emptyMap(), Collections.<BoundField>emptyList());
|
|
|
|
|
|
|
|
/** Maps from JSON member name to field */
|
|
|
|
public final Map<String, BoundField> deserializedFields;
|
2023-11-06 20:59:01 +01:00
|
|
|
|
2023-09-30 22:23:31 +02:00
|
|
|
public final List<BoundField> serializedFields;
|
|
|
|
|
|
|
|
public FieldsData(
|
|
|
|
Map<String, BoundField> deserializedFields, List<BoundField> serializedFields) {
|
|
|
|
this.deserializedFields = deserializedFields;
|
|
|
|
this.serializedFields = serializedFields;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
private static IllegalArgumentException createDuplicateFieldException(
|
|
|
|
Class<?> declaringType, String duplicateName, Field field1, Field field2) {
|
|
|
|
throw new IllegalArgumentException(
|
|
|
|
"Class "
|
|
|
|
+ declaringType.getName()
|
|
|
|
+ " declares multiple JSON fields named '"
|
|
|
|
+ duplicateName
|
2023-11-15 00:09:54 +01:00
|
|
|
+ "'; conflict is caused by fields "
|
2023-09-30 22:23:31 +02:00
|
|
|
+ ReflectionHelper.fieldToString(field1)
|
|
|
|
+ " and "
|
|
|
|
+ ReflectionHelper.fieldToString(field2)
|
|
|
|
+ "\nSee "
|
|
|
|
+ TroubleshootingGuide.createUrl("duplicate-fields"));
|
|
|
|
}
|
|
|
|
|
|
|
|
private FieldsData getBoundFields(
|
|
|
|
Gson context, TypeToken<?> type, Class<?> raw, boolean blockInaccessible, boolean isRecord) {
|
2011-09-11 09:04:56 +02:00
|
|
|
if (raw.isInterface()) {
|
2023-09-30 22:23:31 +02:00
|
|
|
return FieldsData.EMPTY;
|
2011-09-11 09:04:56 +02:00
|
|
|
}
|
|
|
|
|
2023-09-30 22:23:31 +02:00
|
|
|
Map<String, BoundField> deserializedFields = new LinkedHashMap<>();
|
|
|
|
// For serialized fields use a Map to track duplicate field names; otherwise this could be a
|
|
|
|
// List<BoundField> instead
|
|
|
|
Map<String, BoundField> serializedFields = new LinkedHashMap<>();
|
|
|
|
|
2022-04-17 18:05:18 +02:00
|
|
|
Class<?> originalRaw = raw;
|
2011-09-11 09:04:56 +02:00
|
|
|
while (raw != Object.class) {
|
|
|
|
Field[] fields = raw.getDeclaredFields();
|
2022-04-17 18:05:18 +02:00
|
|
|
|
|
|
|
// For inherited fields, check if access to their declaring class is allowed
|
|
|
|
if (raw != originalRaw && fields.length > 0) {
|
|
|
|
FilterResult filterResult =
|
|
|
|
ReflectionAccessFilterHelper.getFilterResult(reflectionFilters, raw);
|
|
|
|
if (filterResult == FilterResult.BLOCK_ALL) {
|
2022-10-22 18:01:56 +02:00
|
|
|
throw new JsonIOException(
|
|
|
|
"ReflectionAccessFilter does not permit using reflection for "
|
|
|
|
+ raw
|
|
|
|
+ " (supertype of "
|
|
|
|
+ originalRaw
|
2023-11-15 00:09:54 +01:00
|
|
|
+ "). Register a TypeAdapter for this type or adjust the access filter.");
|
2022-04-17 18:05:18 +02:00
|
|
|
}
|
|
|
|
blockInaccessible = filterResult == FilterResult.BLOCK_INACCESSIBLE;
|
|
|
|
}
|
|
|
|
|
2011-09-11 09:04:56 +02:00
|
|
|
for (Field field : fields) {
|
2022-07-21 20:53:52 +02:00
|
|
|
boolean serialize = includeField(field, true);
|
|
|
|
boolean deserialize = includeField(field, false);
|
2011-09-11 09:04:56 +02:00
|
|
|
if (!serialize && !deserialize) {
|
|
|
|
continue;
|
|
|
|
}
|
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 the JVM (via reflection), and if so, we will create a special
RecordAdapter to deserialize records, using the canoncial constructor.
The ReflectionTypeAdapterFactory had to be refactored a bit to support
this. The Adapter class inside the factory is now abstract, with
concrete implementations for normal field reflection and for Records.
The common code is in the Adapter, with each implementation
deserializing values into an intermediary object.
For the FieldReflectionAdapter, the intermediary is actually the final
result, and field access is used to write to fields as before. For the
RecordAdapter the intermediary is the Object[] to pass to the Record
constructor.
* Fixed comments from @Marcono1234
Also updated so that we now use the record accessor method to read out
values from a record, so that direct field access is not necessary.
Also added some tests, that should only execute on Java versions with
record support, and be ignored for other JVMs
* Fixed additional comments from @Marcono1234
* Made Adapter in ReflectiveTypeAdapterFactory public
Fix comment from @eamonnmcmanus
2022-10-11 18:13:49 +02:00
|
|
|
// The accessor method is only used for records. If the type is a record, we will read out
|
|
|
|
// values via its accessor method instead of via reflection. This way we will bypass the
|
|
|
|
// accessible restrictions
|
|
|
|
Method accessor = null;
|
2022-10-22 18:01:56 +02:00
|
|
|
if (isRecord) {
|
|
|
|
// If there is a static field on a record, there will not be an accessor. Instead we will
|
|
|
|
// use the default field serialization logic, but for deserialization the field is
|
|
|
|
// excluded for simplicity.
|
|
|
|
// Note that Gson ignores static fields by default, but
|
|
|
|
// GsonBuilder.excludeFieldsWithModifiers can overwrite this.
|
|
|
|
if (Modifier.isStatic(field.getModifiers())) {
|
|
|
|
deserialize = false;
|
|
|
|
} else {
|
|
|
|
accessor = ReflectionHelper.getAccessor(raw, field);
|
|
|
|
// If blockInaccessible, skip and perform access check later
|
|
|
|
if (!blockInaccessible) {
|
|
|
|
ReflectionHelper.makeAccessible(accessor);
|
|
|
|
}
|
|
|
|
|
|
|
|
// @SerializedName can be placed on accessor method, but it is not supported there
|
|
|
|
// If field and method have annotation it is not easily possible to determine if
|
|
|
|
// accessor method is implicit and has inherited annotation, or if it is explicitly
|
|
|
|
// declared with custom annotation
|
|
|
|
if (accessor.getAnnotation(SerializedName.class) != null
|
|
|
|
&& field.getAnnotation(SerializedName.class) == null) {
|
|
|
|
String methodDescription =
|
|
|
|
ReflectionHelper.getAccessibleObjectDescription(accessor, false);
|
|
|
|
throw new JsonIOException(
|
|
|
|
"@SerializedName on " + methodDescription + " is not supported");
|
|
|
|
}
|
|
|
|
}
|
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 the JVM (via reflection), and if so, we will create a special
RecordAdapter to deserialize records, using the canoncial constructor.
The ReflectionTypeAdapterFactory had to be refactored a bit to support
this. The Adapter class inside the factory is now abstract, with
concrete implementations for normal field reflection and for Records.
The common code is in the Adapter, with each implementation
deserializing values into an intermediary object.
For the FieldReflectionAdapter, the intermediary is actually the final
result, and field access is used to write to fields as before. For the
RecordAdapter the intermediary is the Object[] to pass to the Record
constructor.
* Fixed comments from @Marcono1234
Also updated so that we now use the record accessor method to read out
values from a record, so that direct field access is not necessary.
Also added some tests, that should only execute on Java versions with
record support, and be ignored for other JVMs
* Fixed additional comments from @Marcono1234
* Made Adapter in ReflectiveTypeAdapterFactory public
Fix comment from @eamonnmcmanus
2022-10-11 18:13:49 +02:00
|
|
|
}
|
2022-04-17 18:05:18 +02:00
|
|
|
|
2022-10-22 18:01:56 +02:00
|
|
|
// If blockInaccessible, skip and perform access check later
|
|
|
|
// For Records if the accessor method is used the field does not have to be made accessible
|
|
|
|
if (!blockInaccessible && accessor == null) {
|
2022-04-17 18:05:18 +02:00
|
|
|
ReflectionHelper.makeAccessible(field);
|
|
|
|
}
|
2023-09-30 22:23:31 +02:00
|
|
|
|
2011-09-11 09:04:56 +02:00
|
|
|
Type fieldType = $Gson$Types.resolve(type.getType(), raw, field.getGenericType());
|
2015-10-03 11:01:16 +02:00
|
|
|
List<String> fieldNames = getFieldNames(field);
|
2023-09-30 22:23:31 +02:00
|
|
|
String serializedName = fieldNames.get(0);
|
|
|
|
BoundField boundField =
|
|
|
|
createBoundField(
|
|
|
|
context,
|
|
|
|
field,
|
|
|
|
accessor,
|
|
|
|
serializedName,
|
|
|
|
TypeToken.get(fieldType),
|
|
|
|
serialize,
|
|
|
|
blockInaccessible);
|
|
|
|
|
|
|
|
if (deserialize) {
|
|
|
|
for (String name : fieldNames) {
|
|
|
|
BoundField replaced = deserializedFields.put(name, boundField);
|
|
|
|
|
|
|
|
if (replaced != null) {
|
|
|
|
throw createDuplicateFieldException(originalRaw, name, replaced.field, field);
|
|
|
|
}
|
|
|
|
}
|
2015-10-03 11:01:16 +02:00
|
|
|
}
|
2023-09-30 22:23:31 +02:00
|
|
|
|
|
|
|
if (serialize) {
|
|
|
|
BoundField replaced = serializedFields.put(serializedName, boundField);
|
|
|
|
if (replaced != null) {
|
|
|
|
throw createDuplicateFieldException(originalRaw, serializedName, replaced.field, field);
|
|
|
|
}
|
2011-09-11 09:04:56 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
type = TypeToken.get($Gson$Types.resolve(type.getType(), raw, raw.getGenericSuperclass()));
|
|
|
|
raw = type.getRawType();
|
|
|
|
}
|
2023-09-30 22:23:31 +02:00
|
|
|
return new FieldsData(deserializedFields, new ArrayList<>(serializedFields.values()));
|
2011-09-11 09:04:56 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
abstract static class BoundField {
|
2023-09-30 22:23:31 +02:00
|
|
|
/** Name used for serialization (but not for deserialization) */
|
|
|
|
final String serializedName;
|
2023-11-06 20:59:01 +01:00
|
|
|
|
2022-12-05 02:27:43 +01:00
|
|
|
final Field field;
|
2023-11-06 20:59:01 +01:00
|
|
|
|
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 the JVM (via reflection), and if so, we will create a special
RecordAdapter to deserialize records, using the canoncial constructor.
The ReflectionTypeAdapterFactory had to be refactored a bit to support
this. The Adapter class inside the factory is now abstract, with
concrete implementations for normal field reflection and for Records.
The common code is in the Adapter, with each implementation
deserializing values into an intermediary object.
For the FieldReflectionAdapter, the intermediary is actually the final
result, and field access is used to write to fields as before. For the
RecordAdapter the intermediary is the Object[] to pass to the Record
constructor.
* Fixed comments from @Marcono1234
Also updated so that we now use the record accessor method to read out
values from a record, so that direct field access is not necessary.
Also added some tests, that should only execute on Java versions with
record support, and be ignored for other JVMs
* Fixed additional comments from @Marcono1234
* Made Adapter in ReflectiveTypeAdapterFactory public
Fix comment from @eamonnmcmanus
2022-10-11 18:13:49 +02:00
|
|
|
/** Name of the underlying field */
|
|
|
|
final String fieldName;
|
2011-09-11 09:04:56 +02:00
|
|
|
|
2023-09-30 22:23:31 +02:00
|
|
|
protected BoundField(String serializedName, Field field) {
|
|
|
|
this.serializedName = serializedName;
|
2022-12-05 02:27:43 +01:00
|
|
|
this.field = field;
|
|
|
|
this.fieldName = field.getName();
|
2011-09-11 09:04:56 +02:00
|
|
|
}
|
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 the JVM (via reflection), and if so, we will create a special
RecordAdapter to deserialize records, using the canoncial constructor.
The ReflectionTypeAdapterFactory had to be refactored a bit to support
this. The Adapter class inside the factory is now abstract, with
concrete implementations for normal field reflection and for Records.
The common code is in the Adapter, with each implementation
deserializing values into an intermediary object.
For the FieldReflectionAdapter, the intermediary is actually the final
result, and field access is used to write to fields as before. For the
RecordAdapter the intermediary is the Object[] to pass to the Record
constructor.
* Fixed comments from @Marcono1234
Also updated so that we now use the record accessor method to read out
values from a record, so that direct field access is not necessary.
Also added some tests, that should only execute on Java versions with
record support, and be ignored for other JVMs
* Fixed additional comments from @Marcono1234
* Made Adapter in ReflectiveTypeAdapterFactory public
Fix comment from @eamonnmcmanus
2022-10-11 18:13:49 +02:00
|
|
|
|
|
|
|
/** Read this field value from the source, and append its JSON value to the writer */
|
2022-10-22 18:01:56 +02:00
|
|
|
abstract void write(JsonWriter writer, Object source)
|
|
|
|
throws IOException, IllegalAccessException;
|
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 the JVM (via reflection), and if so, we will create a special
RecordAdapter to deserialize records, using the canoncial constructor.
The ReflectionTypeAdapterFactory had to be refactored a bit to support
this. The Adapter class inside the factory is now abstract, with
concrete implementations for normal field reflection and for Records.
The common code is in the Adapter, with each implementation
deserializing values into an intermediary object.
For the FieldReflectionAdapter, the intermediary is actually the final
result, and field access is used to write to fields as before. For the
RecordAdapter the intermediary is the Object[] to pass to the Record
constructor.
* Fixed comments from @Marcono1234
Also updated so that we now use the record accessor method to read out
values from a record, so that direct field access is not necessary.
Also added some tests, that should only execute on Java versions with
record support, and be ignored for other JVMs
* Fixed additional comments from @Marcono1234
* Made Adapter in ReflectiveTypeAdapterFactory public
Fix comment from @eamonnmcmanus
2022-10-11 18:13:49 +02:00
|
|
|
|
|
|
|
/** Read the value into the target array, used to provide constructor arguments for records */
|
2022-10-22 18:01:56 +02:00
|
|
|
abstract void readIntoArray(JsonReader reader, int index, Object[] target)
|
|
|
|
throws IOException, JsonParseException;
|
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 the JVM (via reflection), and if so, we will create a special
RecordAdapter to deserialize records, using the canoncial constructor.
The ReflectionTypeAdapterFactory had to be refactored a bit to support
this. The Adapter class inside the factory is now abstract, with
concrete implementations for normal field reflection and for Records.
The common code is in the Adapter, with each implementation
deserializing values into an intermediary object.
For the FieldReflectionAdapter, the intermediary is actually the final
result, and field access is used to write to fields as before. For the
RecordAdapter the intermediary is the Object[] to pass to the Record
constructor.
* Fixed comments from @Marcono1234
Also updated so that we now use the record accessor method to read out
values from a record, so that direct field access is not necessary.
Also added some tests, that should only execute on Java versions with
record support, and be ignored for other JVMs
* Fixed additional comments from @Marcono1234
* Made Adapter in ReflectiveTypeAdapterFactory public
Fix comment from @eamonnmcmanus
2022-10-11 18:13:49 +02:00
|
|
|
|
|
|
|
/**
|
|
|
|
* Read the value from the reader, and set it on the corresponding field on target via
|
|
|
|
* reflection
|
|
|
|
*/
|
|
|
|
abstract void readIntoField(JsonReader reader, Object target)
|
|
|
|
throws IOException, IllegalAccessException;
|
2011-09-11 09:04:56 +02:00
|
|
|
}
|
|
|
|
|
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 the JVM (via reflection), and if so, we will create a special
RecordAdapter to deserialize records, using the canoncial constructor.
The ReflectionTypeAdapterFactory had to be refactored a bit to support
this. The Adapter class inside the factory is now abstract, with
concrete implementations for normal field reflection and for Records.
The common code is in the Adapter, with each implementation
deserializing values into an intermediary object.
For the FieldReflectionAdapter, the intermediary is actually the final
result, and field access is used to write to fields as before. For the
RecordAdapter the intermediary is the Object[] to pass to the Record
constructor.
* Fixed comments from @Marcono1234
Also updated so that we now use the record accessor method to read out
values from a record, so that direct field access is not necessary.
Also added some tests, that should only execute on Java versions with
record support, and be ignored for other JVMs
* Fixed additional comments from @Marcono1234
* Made Adapter in ReflectiveTypeAdapterFactory public
Fix comment from @eamonnmcmanus
2022-10-11 18:13:49 +02:00
|
|
|
/**
|
|
|
|
* Base class for Adapters produced by this factory.
|
|
|
|
*
|
|
|
|
* <p>The {@link RecordAdapter} is a special case to handle records for JVMs that support it, for
|
|
|
|
* all other types we use the {@link FieldReflectionAdapter}. This class encapsulates the common
|
|
|
|
* logic for serialization and deserialization. During deserialization, we construct an
|
|
|
|
* accumulator A, which we use to accumulate values from the source JSON. After the object has
|
|
|
|
* been read in full, the {@link #finalize(Object)} method is used to convert the accumulator to
|
|
|
|
* an instance of T.
|
|
|
|
*
|
|
|
|
* @param <T> type of objects that this Adapter creates.
|
|
|
|
* @param <A> type of accumulator used to build the deserialization result.
|
|
|
|
*/
|
2022-10-22 18:01:56 +02:00
|
|
|
// This class is public because external projects check for this class with `instanceof` (even
|
|
|
|
// though it is internal)
|
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 the JVM (via reflection), and if so, we will create a special
RecordAdapter to deserialize records, using the canoncial constructor.
The ReflectionTypeAdapterFactory had to be refactored a bit to support
this. The Adapter class inside the factory is now abstract, with
concrete implementations for normal field reflection and for Records.
The common code is in the Adapter, with each implementation
deserializing values into an intermediary object.
For the FieldReflectionAdapter, the intermediary is actually the final
result, and field access is used to write to fields as before. For the
RecordAdapter the intermediary is the Object[] to pass to the Record
constructor.
* Fixed comments from @Marcono1234
Also updated so that we now use the record accessor method to read out
values from a record, so that direct field access is not necessary.
Also added some tests, that should only execute on Java versions with
record support, and be ignored for other JVMs
* Fixed additional comments from @Marcono1234
* Made Adapter in ReflectiveTypeAdapterFactory public
Fix comment from @eamonnmcmanus
2022-10-11 18:13:49 +02:00
|
|
|
public abstract static class Adapter<T, A> extends TypeAdapter<T> {
|
2023-09-30 22:23:31 +02:00
|
|
|
private final FieldsData fieldsData;
|
2011-09-11 09:04:56 +02:00
|
|
|
|
2023-09-30 22:23:31 +02:00
|
|
|
Adapter(FieldsData fieldsData) {
|
|
|
|
this.fieldsData = fieldsData;
|
2011-09-11 09:04:56 +02:00
|
|
|
}
|
|
|
|
|
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 the JVM (via reflection), and if so, we will create a special
RecordAdapter to deserialize records, using the canoncial constructor.
The ReflectionTypeAdapterFactory had to be refactored a bit to support
this. The Adapter class inside the factory is now abstract, with
concrete implementations for normal field reflection and for Records.
The common code is in the Adapter, with each implementation
deserializing values into an intermediary object.
For the FieldReflectionAdapter, the intermediary is actually the final
result, and field access is used to write to fields as before. For the
RecordAdapter the intermediary is the Object[] to pass to the Record
constructor.
* Fixed comments from @Marcono1234
Also updated so that we now use the record accessor method to read out
values from a record, so that direct field access is not necessary.
Also added some tests, that should only execute on Java versions with
record support, and be ignored for other JVMs
* Fixed additional comments from @Marcono1234
* Made Adapter in ReflectiveTypeAdapterFactory public
Fix comment from @eamonnmcmanus
2022-10-11 18:13:49 +02:00
|
|
|
@Override
|
|
|
|
public void write(JsonWriter out, T value) throws IOException {
|
|
|
|
if (value == null) {
|
|
|
|
out.nullValue();
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
out.beginObject();
|
|
|
|
try {
|
2023-09-30 22:23:31 +02:00
|
|
|
for (BoundField boundField : fieldsData.serializedFields) {
|
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 the JVM (via reflection), and if so, we will create a special
RecordAdapter to deserialize records, using the canoncial constructor.
The ReflectionTypeAdapterFactory had to be refactored a bit to support
this. The Adapter class inside the factory is now abstract, with
concrete implementations for normal field reflection and for Records.
The common code is in the Adapter, with each implementation
deserializing values into an intermediary object.
For the FieldReflectionAdapter, the intermediary is actually the final
result, and field access is used to write to fields as before. For the
RecordAdapter the intermediary is the Object[] to pass to the Record
constructor.
* Fixed comments from @Marcono1234
Also updated so that we now use the record accessor method to read out
values from a record, so that direct field access is not necessary.
Also added some tests, that should only execute on Java versions with
record support, and be ignored for other JVMs
* Fixed additional comments from @Marcono1234
* Made Adapter in ReflectiveTypeAdapterFactory public
Fix comment from @eamonnmcmanus
2022-10-11 18:13:49 +02:00
|
|
|
boundField.write(out, value);
|
|
|
|
}
|
|
|
|
} catch (IllegalAccessException e) {
|
|
|
|
throw ReflectionHelper.createExceptionForUnexpectedIllegalAccess(e);
|
|
|
|
}
|
|
|
|
out.endObject();
|
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
public T read(JsonReader in) throws IOException {
|
2011-12-03 20:46:25 +01:00
|
|
|
if (in.peek() == JsonToken.NULL) {
|
|
|
|
in.nextNull();
|
2011-09-11 09:04:56 +02:00
|
|
|
return null;
|
|
|
|
}
|
|
|
|
|
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 the JVM (via reflection), and if so, we will create a special
RecordAdapter to deserialize records, using the canoncial constructor.
The ReflectionTypeAdapterFactory had to be refactored a bit to support
this. The Adapter class inside the factory is now abstract, with
concrete implementations for normal field reflection and for Records.
The common code is in the Adapter, with each implementation
deserializing values into an intermediary object.
For the FieldReflectionAdapter, the intermediary is actually the final
result, and field access is used to write to fields as before. For the
RecordAdapter the intermediary is the Object[] to pass to the Record
constructor.
* Fixed comments from @Marcono1234
Also updated so that we now use the record accessor method to read out
values from a record, so that direct field access is not necessary.
Also added some tests, that should only execute on Java versions with
record support, and be ignored for other JVMs
* Fixed additional comments from @Marcono1234
* Made Adapter in ReflectiveTypeAdapterFactory public
Fix comment from @eamonnmcmanus
2022-10-11 18:13:49 +02:00
|
|
|
A accumulator = createAccumulator();
|
2023-09-30 22:23:31 +02:00
|
|
|
Map<String, BoundField> deserializedFields = fieldsData.deserializedFields;
|
2011-09-11 09:04:56 +02:00
|
|
|
|
|
|
|
try {
|
2011-12-03 20:46:25 +01:00
|
|
|
in.beginObject();
|
|
|
|
while (in.hasNext()) {
|
|
|
|
String name = in.nextName();
|
2023-09-30 22:23:31 +02:00
|
|
|
BoundField field = deserializedFields.get(name);
|
|
|
|
if (field == null) {
|
2011-12-03 20:46:25 +01:00
|
|
|
in.skipValue();
|
2011-09-11 09:04:56 +02:00
|
|
|
} else {
|
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 the JVM (via reflection), and if so, we will create a special
RecordAdapter to deserialize records, using the canoncial constructor.
The ReflectionTypeAdapterFactory had to be refactored a bit to support
this. The Adapter class inside the factory is now abstract, with
concrete implementations for normal field reflection and for Records.
The common code is in the Adapter, with each implementation
deserializing values into an intermediary object.
For the FieldReflectionAdapter, the intermediary is actually the final
result, and field access is used to write to fields as before. For the
RecordAdapter the intermediary is the Object[] to pass to the Record
constructor.
* Fixed comments from @Marcono1234
Also updated so that we now use the record accessor method to read out
values from a record, so that direct field access is not necessary.
Also added some tests, that should only execute on Java versions with
record support, and be ignored for other JVMs
* Fixed additional comments from @Marcono1234
* Made Adapter in ReflectiveTypeAdapterFactory public
Fix comment from @eamonnmcmanus
2022-10-11 18:13:49 +02:00
|
|
|
readField(accumulator, in, field);
|
2011-09-11 09:04:56 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
} catch (IllegalStateException e) {
|
|
|
|
throw new JsonSyntaxException(e);
|
|
|
|
} catch (IllegalAccessException e) {
|
2022-04-17 18:05:18 +02:00
|
|
|
throw ReflectionHelper.createExceptionForUnexpectedIllegalAccess(e);
|
2011-09-11 09:04:56 +02:00
|
|
|
}
|
2011-12-03 20:46:25 +01:00
|
|
|
in.endObject();
|
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 the JVM (via reflection), and if so, we will create a special
RecordAdapter to deserialize records, using the canoncial constructor.
The ReflectionTypeAdapterFactory had to be refactored a bit to support
this. The Adapter class inside the factory is now abstract, with
concrete implementations for normal field reflection and for Records.
The common code is in the Adapter, with each implementation
deserializing values into an intermediary object.
For the FieldReflectionAdapter, the intermediary is actually the final
result, and field access is used to write to fields as before. For the
RecordAdapter the intermediary is the Object[] to pass to the Record
constructor.
* Fixed comments from @Marcono1234
Also updated so that we now use the record accessor method to read out
values from a record, so that direct field access is not necessary.
Also added some tests, that should only execute on Java versions with
record support, and be ignored for other JVMs
* Fixed additional comments from @Marcono1234
* Made Adapter in ReflectiveTypeAdapterFactory public
Fix comment from @eamonnmcmanus
2022-10-11 18:13:49 +02:00
|
|
|
return finalize(accumulator);
|
2011-09-11 09:04:56 +02:00
|
|
|
}
|
|
|
|
|
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 the JVM (via reflection), and if so, we will create a special
RecordAdapter to deserialize records, using the canoncial constructor.
The ReflectionTypeAdapterFactory had to be refactored a bit to support
this. The Adapter class inside the factory is now abstract, with
concrete implementations for normal field reflection and for Records.
The common code is in the Adapter, with each implementation
deserializing values into an intermediary object.
For the FieldReflectionAdapter, the intermediary is actually the final
result, and field access is used to write to fields as before. For the
RecordAdapter the intermediary is the Object[] to pass to the Record
constructor.
* Fixed comments from @Marcono1234
Also updated so that we now use the record accessor method to read out
values from a record, so that direct field access is not necessary.
Also added some tests, that should only execute on Java versions with
record support, and be ignored for other JVMs
* Fixed additional comments from @Marcono1234
* Made Adapter in ReflectiveTypeAdapterFactory public
Fix comment from @eamonnmcmanus
2022-10-11 18:13:49 +02:00
|
|
|
/** Create the Object that will be used to collect each field value */
|
|
|
|
abstract A createAccumulator();
|
2023-11-06 20:59:01 +01:00
|
|
|
|
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 the JVM (via reflection), and if so, we will create a special
RecordAdapter to deserialize records, using the canoncial constructor.
The ReflectionTypeAdapterFactory had to be refactored a bit to support
this. The Adapter class inside the factory is now abstract, with
concrete implementations for normal field reflection and for Records.
The common code is in the Adapter, with each implementation
deserializing values into an intermediary object.
For the FieldReflectionAdapter, the intermediary is actually the final
result, and field access is used to write to fields as before. For the
RecordAdapter the intermediary is the Object[] to pass to the Record
constructor.
* Fixed comments from @Marcono1234
Also updated so that we now use the record accessor method to read out
values from a record, so that direct field access is not necessary.
Also added some tests, that should only execute on Java versions with
record support, and be ignored for other JVMs
* Fixed additional comments from @Marcono1234
* Made Adapter in ReflectiveTypeAdapterFactory public
Fix comment from @eamonnmcmanus
2022-10-11 18:13:49 +02:00
|
|
|
/**
|
2022-10-22 18:01:56 +02:00
|
|
|
* Read a single BoundField into the accumulator. The JsonReader will be pointed at the start of
|
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 the JVM (via reflection), and if so, we will create a special
RecordAdapter to deserialize records, using the canoncial constructor.
The ReflectionTypeAdapterFactory had to be refactored a bit to support
this. The Adapter class inside the factory is now abstract, with
concrete implementations for normal field reflection and for Records.
The common code is in the Adapter, with each implementation
deserializing values into an intermediary object.
For the FieldReflectionAdapter, the intermediary is actually the final
result, and field access is used to write to fields as before. For the
RecordAdapter the intermediary is the Object[] to pass to the Record
constructor.
* Fixed comments from @Marcono1234
Also updated so that we now use the record accessor method to read out
values from a record, so that direct field access is not necessary.
Also added some tests, that should only execute on Java versions with
record support, and be ignored for other JVMs
* Fixed additional comments from @Marcono1234
* Made Adapter in ReflectiveTypeAdapterFactory public
Fix comment from @eamonnmcmanus
2022-10-11 18:13:49 +02:00
|
|
|
* the value for the BoundField to read from.
|
|
|
|
*/
|
|
|
|
abstract void readField(A accumulator, JsonReader in, BoundField field)
|
|
|
|
throws IllegalAccessException, IOException;
|
2023-11-06 20:59:01 +01:00
|
|
|
|
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 the JVM (via reflection), and if so, we will create a special
RecordAdapter to deserialize records, using the canoncial constructor.
The ReflectionTypeAdapterFactory had to be refactored a bit to support
this. The Adapter class inside the factory is now abstract, with
concrete implementations for normal field reflection and for Records.
The common code is in the Adapter, with each implementation
deserializing values into an intermediary object.
For the FieldReflectionAdapter, the intermediary is actually the final
result, and field access is used to write to fields as before. For the
RecordAdapter the intermediary is the Object[] to pass to the Record
constructor.
* Fixed comments from @Marcono1234
Also updated so that we now use the record accessor method to read out
values from a record, so that direct field access is not necessary.
Also added some tests, that should only execute on Java versions with
record support, and be ignored for other JVMs
* Fixed additional comments from @Marcono1234
* Made Adapter in ReflectiveTypeAdapterFactory public
Fix comment from @eamonnmcmanus
2022-10-11 18:13:49 +02:00
|
|
|
/** Convert the accumulator to a final instance of T. */
|
|
|
|
abstract T finalize(A accumulator);
|
|
|
|
}
|
|
|
|
|
|
|
|
private static final class FieldReflectionAdapter<T> extends Adapter<T, T> {
|
|
|
|
private final ObjectConstructor<T> constructor;
|
|
|
|
|
2023-09-30 22:23:31 +02:00
|
|
|
FieldReflectionAdapter(ObjectConstructor<T> constructor, FieldsData fieldsData) {
|
|
|
|
super(fieldsData);
|
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 the JVM (via reflection), and if so, we will create a special
RecordAdapter to deserialize records, using the canoncial constructor.
The ReflectionTypeAdapterFactory had to be refactored a bit to support
this. The Adapter class inside the factory is now abstract, with
concrete implementations for normal field reflection and for Records.
The common code is in the Adapter, with each implementation
deserializing values into an intermediary object.
For the FieldReflectionAdapter, the intermediary is actually the final
result, and field access is used to write to fields as before. For the
RecordAdapter the intermediary is the Object[] to pass to the Record
constructor.
* Fixed comments from @Marcono1234
Also updated so that we now use the record accessor method to read out
values from a record, so that direct field access is not necessary.
Also added some tests, that should only execute on Java versions with
record support, and be ignored for other JVMs
* Fixed additional comments from @Marcono1234
* Made Adapter in ReflectiveTypeAdapterFactory public
Fix comment from @eamonnmcmanus
2022-10-11 18:13:49 +02:00
|
|
|
this.constructor = constructor;
|
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
T createAccumulator() {
|
|
|
|
return constructor.construct();
|
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
void readField(T accumulator, JsonReader in, BoundField field)
|
|
|
|
throws IllegalAccessException, IOException {
|
|
|
|
field.readIntoField(in, accumulator);
|
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
T finalize(T accumulator) {
|
|
|
|
return accumulator;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
private static final class RecordAdapter<T> extends Adapter<T, Object[]> {
|
2022-10-22 18:01:56 +02:00
|
|
|
static final Map<Class<?>, Object> PRIMITIVE_DEFAULTS = primitiveDefaults();
|
2022-10-12 23:24:36 +02:00
|
|
|
|
2022-10-22 18:01:56 +02:00
|
|
|
// The canonical constructor of the record
|
|
|
|
private final Constructor<T> constructor;
|
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 the JVM (via reflection), and if so, we will create a special
RecordAdapter to deserialize records, using the canoncial constructor.
The ReflectionTypeAdapterFactory had to be refactored a bit to support
this. The Adapter class inside the factory is now abstract, with
concrete implementations for normal field reflection and for Records.
The common code is in the Adapter, with each implementation
deserializing values into an intermediary object.
For the FieldReflectionAdapter, the intermediary is actually the final
result, and field access is used to write to fields as before. For the
RecordAdapter the intermediary is the Object[] to pass to the Record
constructor.
* Fixed comments from @Marcono1234
Also updated so that we now use the record accessor method to read out
values from a record, so that direct field access is not necessary.
Also added some tests, that should only execute on Java versions with
record support, and be ignored for other JVMs
* Fixed additional comments from @Marcono1234
* Made Adapter in ReflectiveTypeAdapterFactory public
Fix comment from @eamonnmcmanus
2022-10-11 18:13:49 +02:00
|
|
|
// Array of arguments to the constructor, initialized with default values for primitives
|
|
|
|
private final Object[] constructorArgsDefaults;
|
|
|
|
// Map from component names to index into the constructors arguments.
|
|
|
|
private final Map<String, Integer> componentIndices = new HashMap<>();
|
|
|
|
|
2023-09-30 22:23:31 +02:00
|
|
|
RecordAdapter(Class<T> raw, FieldsData fieldsData, boolean blockInaccessible) {
|
|
|
|
super(fieldsData);
|
2022-10-22 18:01:56 +02:00
|
|
|
constructor = ReflectionHelper.getCanonicalRecordConstructor(raw);
|
|
|
|
|
|
|
|
if (blockInaccessible) {
|
|
|
|
checkAccessible(null, constructor);
|
|
|
|
} else {
|
|
|
|
// Ensure the constructor is accessible
|
|
|
|
ReflectionHelper.makeAccessible(constructor);
|
|
|
|
}
|
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 the JVM (via reflection), and if so, we will create a special
RecordAdapter to deserialize records, using the canoncial constructor.
The ReflectionTypeAdapterFactory had to be refactored a bit to support
this. The Adapter class inside the factory is now abstract, with
concrete implementations for normal field reflection and for Records.
The common code is in the Adapter, with each implementation
deserializing values into an intermediary object.
For the FieldReflectionAdapter, the intermediary is actually the final
result, and field access is used to write to fields as before. For the
RecordAdapter the intermediary is the Object[] to pass to the Record
constructor.
* Fixed comments from @Marcono1234
Also updated so that we now use the record accessor method to read out
values from a record, so that direct field access is not necessary.
Also added some tests, that should only execute on Java versions with
record support, and be ignored for other JVMs
* Fixed additional comments from @Marcono1234
* Made Adapter in ReflectiveTypeAdapterFactory public
Fix comment from @eamonnmcmanus
2022-10-11 18:13:49 +02:00
|
|
|
|
|
|
|
String[] componentNames = ReflectionHelper.getRecordComponentNames(raw);
|
|
|
|
for (int i = 0; i < componentNames.length; i++) {
|
|
|
|
componentIndices.put(componentNames[i], i);
|
2011-09-11 09:04:56 +02:00
|
|
|
}
|
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 the JVM (via reflection), and if so, we will create a special
RecordAdapter to deserialize records, using the canoncial constructor.
The ReflectionTypeAdapterFactory had to be refactored a bit to support
this. The Adapter class inside the factory is now abstract, with
concrete implementations for normal field reflection and for Records.
The common code is in the Adapter, with each implementation
deserializing values into an intermediary object.
For the FieldReflectionAdapter, the intermediary is actually the final
result, and field access is used to write to fields as before. For the
RecordAdapter the intermediary is the Object[] to pass to the Record
constructor.
* Fixed comments from @Marcono1234
Also updated so that we now use the record accessor method to read out
values from a record, so that direct field access is not necessary.
Also added some tests, that should only execute on Java versions with
record support, and be ignored for other JVMs
* Fixed additional comments from @Marcono1234
* Made Adapter in ReflectiveTypeAdapterFactory public
Fix comment from @eamonnmcmanus
2022-10-11 18:13:49 +02:00
|
|
|
Class<?>[] parameterTypes = constructor.getParameterTypes();
|
2011-09-11 09:04:56 +02:00
|
|
|
|
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 the JVM (via reflection), and if so, we will create a special
RecordAdapter to deserialize records, using the canoncial constructor.
The ReflectionTypeAdapterFactory had to be refactored a bit to support
this. The Adapter class inside the factory is now abstract, with
concrete implementations for normal field reflection and for Records.
The common code is in the Adapter, with each implementation
deserializing values into an intermediary object.
For the FieldReflectionAdapter, the intermediary is actually the final
result, and field access is used to write to fields as before. For the
RecordAdapter the intermediary is the Object[] to pass to the Record
constructor.
* Fixed comments from @Marcono1234
Also updated so that we now use the record accessor method to read out
values from a record, so that direct field access is not necessary.
Also added some tests, that should only execute on Java versions with
record support, and be ignored for other JVMs
* Fixed additional comments from @Marcono1234
* Made Adapter in ReflectiveTypeAdapterFactory public
Fix comment from @eamonnmcmanus
2022-10-11 18:13:49 +02:00
|
|
|
// We need to ensure that we are passing non-null values to primitive fields in the
|
|
|
|
// constructor. To do this, we create an Object[] where all primitives are initialized to
|
|
|
|
// non-null values.
|
|
|
|
constructorArgsDefaults = new Object[parameterTypes.length];
|
|
|
|
for (int i = 0; i < parameterTypes.length; i++) {
|
2022-10-12 23:24:36 +02:00
|
|
|
// This will correctly be null for non-primitive types:
|
|
|
|
constructorArgsDefaults[i] = PRIMITIVE_DEFAULTS.get(parameterTypes[i]);
|
2011-09-11 09:04:56 +02:00
|
|
|
}
|
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 the JVM (via reflection), and if so, we will create a special
RecordAdapter to deserialize records, using the canoncial constructor.
The ReflectionTypeAdapterFactory had to be refactored a bit to support
this. The Adapter class inside the factory is now abstract, with
concrete implementations for normal field reflection and for Records.
The common code is in the Adapter, with each implementation
deserializing values into an intermediary object.
For the FieldReflectionAdapter, the intermediary is actually the final
result, and field access is used to write to fields as before. For the
RecordAdapter the intermediary is the Object[] to pass to the Record
constructor.
* Fixed comments from @Marcono1234
Also updated so that we now use the record accessor method to read out
values from a record, so that direct field access is not necessary.
Also added some tests, that should only execute on Java versions with
record support, and be ignored for other JVMs
* Fixed additional comments from @Marcono1234
* Made Adapter in ReflectiveTypeAdapterFactory public
Fix comment from @eamonnmcmanus
2022-10-11 18:13:49 +02:00
|
|
|
}
|
|
|
|
|
2022-10-12 23:24:36 +02:00
|
|
|
private static Map<Class<?>, Object> primitiveDefaults() {
|
|
|
|
Map<Class<?>, Object> zeroes = new HashMap<>();
|
|
|
|
zeroes.put(byte.class, (byte) 0);
|
|
|
|
zeroes.put(short.class, (short) 0);
|
|
|
|
zeroes.put(int.class, 0);
|
|
|
|
zeroes.put(long.class, 0L);
|
|
|
|
zeroes.put(float.class, 0F);
|
|
|
|
zeroes.put(double.class, 0D);
|
|
|
|
zeroes.put(char.class, '\0');
|
|
|
|
zeroes.put(boolean.class, false);
|
|
|
|
return zeroes;
|
|
|
|
}
|
|
|
|
|
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 the JVM (via reflection), and if so, we will create a special
RecordAdapter to deserialize records, using the canoncial constructor.
The ReflectionTypeAdapterFactory had to be refactored a bit to support
this. The Adapter class inside the factory is now abstract, with
concrete implementations for normal field reflection and for Records.
The common code is in the Adapter, with each implementation
deserializing values into an intermediary object.
For the FieldReflectionAdapter, the intermediary is actually the final
result, and field access is used to write to fields as before. For the
RecordAdapter the intermediary is the Object[] to pass to the Record
constructor.
* Fixed comments from @Marcono1234
Also updated so that we now use the record accessor method to read out
values from a record, so that direct field access is not necessary.
Also added some tests, that should only execute on Java versions with
record support, and be ignored for other JVMs
* Fixed additional comments from @Marcono1234
* Made Adapter in ReflectiveTypeAdapterFactory public
Fix comment from @eamonnmcmanus
2022-10-11 18:13:49 +02:00
|
|
|
@Override
|
|
|
|
Object[] createAccumulator() {
|
|
|
|
return constructorArgsDefaults.clone();
|
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
void readField(Object[] accumulator, JsonReader in, BoundField field) throws IOException {
|
2022-10-22 18:01:56 +02:00
|
|
|
// Obtain the component index from the name of the field backing it
|
|
|
|
Integer componentIndex = componentIndices.get(field.fieldName);
|
|
|
|
if (componentIndex == null) {
|
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 the JVM (via reflection), and if so, we will create a special
RecordAdapter to deserialize records, using the canoncial constructor.
The ReflectionTypeAdapterFactory had to be refactored a bit to support
this. The Adapter class inside the factory is now abstract, with
concrete implementations for normal field reflection and for Records.
The common code is in the Adapter, with each implementation
deserializing values into an intermediary object.
For the FieldReflectionAdapter, the intermediary is actually the final
result, and field access is used to write to fields as before. For the
RecordAdapter the intermediary is the Object[] to pass to the Record
constructor.
* Fixed comments from @Marcono1234
Also updated so that we now use the record accessor method to read out
values from a record, so that direct field access is not necessary.
Also added some tests, that should only execute on Java versions with
record support, and be ignored for other JVMs
* Fixed additional comments from @Marcono1234
* Made Adapter in ReflectiveTypeAdapterFactory public
Fix comment from @eamonnmcmanus
2022-10-11 18:13:49 +02:00
|
|
|
throw new IllegalStateException(
|
2022-10-22 18:01:56 +02:00
|
|
|
"Could not find the index in the constructor '"
|
|
|
|
+ ReflectionHelper.constructorToString(constructor)
|
2023-11-15 00:09:54 +01:00
|
|
|
+ "' for field with name '"
|
2022-10-22 18:01:56 +02:00
|
|
|
+ field.fieldName
|
|
|
|
+ "', unable to determine which argument in the constructor the field corresponds"
|
|
|
|
+ " to. This is unexpected behavior, as we expect the RecordComponents to have the"
|
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 the JVM (via reflection), and if so, we will create a special
RecordAdapter to deserialize records, using the canoncial constructor.
The ReflectionTypeAdapterFactory had to be refactored a bit to support
this. The Adapter class inside the factory is now abstract, with
concrete implementations for normal field reflection and for Records.
The common code is in the Adapter, with each implementation
deserializing values into an intermediary object.
For the FieldReflectionAdapter, the intermediary is actually the final
result, and field access is used to write to fields as before. For the
RecordAdapter the intermediary is the Object[] to pass to the Record
constructor.
* Fixed comments from @Marcono1234
Also updated so that we now use the record accessor method to read out
values from a record, so that direct field access is not necessary.
Also added some tests, that should only execute on Java versions with
record support, and be ignored for other JVMs
* Fixed additional comments from @Marcono1234
* Made Adapter in ReflectiveTypeAdapterFactory public
Fix comment from @eamonnmcmanus
2022-10-11 18:13:49 +02:00
|
|
|
+ " same names as the fields in the Java class, and that the order of the"
|
2022-10-22 18:01:56 +02:00
|
|
|
+ " RecordComponents is the same as the order of the canonical constructor"
|
|
|
|
+ " parameters.");
|
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 the JVM (via reflection), and if so, we will create a special
RecordAdapter to deserialize records, using the canoncial constructor.
The ReflectionTypeAdapterFactory had to be refactored a bit to support
this. The Adapter class inside the factory is now abstract, with
concrete implementations for normal field reflection and for Records.
The common code is in the Adapter, with each implementation
deserializing values into an intermediary object.
For the FieldReflectionAdapter, the intermediary is actually the final
result, and field access is used to write to fields as before. For the
RecordAdapter the intermediary is the Object[] to pass to the Record
constructor.
* Fixed comments from @Marcono1234
Also updated so that we now use the record accessor method to read out
values from a record, so that direct field access is not necessary.
Also added some tests, that should only execute on Java versions with
record support, and be ignored for other JVMs
* Fixed additional comments from @Marcono1234
* Made Adapter in ReflectiveTypeAdapterFactory public
Fix comment from @eamonnmcmanus
2022-10-11 18:13:49 +02:00
|
|
|
}
|
2022-10-22 18:01:56 +02:00
|
|
|
field.readIntoArray(in, componentIndex, accumulator);
|
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 the JVM (via reflection), and if so, we will create a special
RecordAdapter to deserialize records, using the canoncial constructor.
The ReflectionTypeAdapterFactory had to be refactored a bit to support
this. The Adapter class inside the factory is now abstract, with
concrete implementations for normal field reflection and for Records.
The common code is in the Adapter, with each implementation
deserializing values into an intermediary object.
For the FieldReflectionAdapter, the intermediary is actually the final
result, and field access is used to write to fields as before. For the
RecordAdapter the intermediary is the Object[] to pass to the Record
constructor.
* Fixed comments from @Marcono1234
Also updated so that we now use the record accessor method to read out
values from a record, so that direct field access is not necessary.
Also added some tests, that should only execute on Java versions with
record support, and be ignored for other JVMs
* Fixed additional comments from @Marcono1234
* Made Adapter in ReflectiveTypeAdapterFactory public
Fix comment from @eamonnmcmanus
2022-10-11 18:13:49 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
T finalize(Object[] accumulator) {
|
|
|
|
try {
|
2022-10-22 18:01:56 +02:00
|
|
|
return constructor.newInstance(accumulator);
|
|
|
|
} catch (IllegalAccessException e) {
|
|
|
|
throw ReflectionHelper.createExceptionForUnexpectedIllegalAccess(e);
|
|
|
|
}
|
|
|
|
// Note: InstantiationException should be impossible because record class is not abstract;
|
|
|
|
// IllegalArgumentException should not be possible unless a bad adapter returns objects of
|
2023-11-15 00:09:54 +01:00
|
|
|
// the wrong type
|
2022-10-22 18:01:56 +02:00
|
|
|
catch (InstantiationException | IllegalArgumentException e) {
|
|
|
|
throw new RuntimeException(
|
|
|
|
"Failed to invoke constructor '"
|
|
|
|
+ ReflectionHelper.constructorToString(constructor)
|
2023-11-15 00:09:54 +01:00
|
|
|
+ "' with args "
|
2022-10-22 18:01:56 +02:00
|
|
|
+ Arrays.toString(accumulator),
|
|
|
|
e);
|
|
|
|
} catch (InvocationTargetException e) {
|
|
|
|
// TODO: JsonParseException ?
|
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 the JVM (via reflection), and if so, we will create a special
RecordAdapter to deserialize records, using the canoncial constructor.
The ReflectionTypeAdapterFactory had to be refactored a bit to support
this. The Adapter class inside the factory is now abstract, with
concrete implementations for normal field reflection and for Records.
The common code is in the Adapter, with each implementation
deserializing values into an intermediary object.
For the FieldReflectionAdapter, the intermediary is actually the final
result, and field access is used to write to fields as before. For the
RecordAdapter the intermediary is the Object[] to pass to the Record
constructor.
* Fixed comments from @Marcono1234
Also updated so that we now use the record accessor method to read out
values from a record, so that direct field access is not necessary.
Also added some tests, that should only execute on Java versions with
record support, and be ignored for other JVMs
* Fixed additional comments from @Marcono1234
* Made Adapter in ReflectiveTypeAdapterFactory public
Fix comment from @eamonnmcmanus
2022-10-11 18:13:49 +02:00
|
|
|
throw new RuntimeException(
|
2022-10-22 18:01:56 +02:00
|
|
|
"Failed to invoke constructor '"
|
|
|
|
+ ReflectionHelper.constructorToString(constructor)
|
2023-11-15 00:09:54 +01:00
|
|
|
+ "' with args "
|
2022-10-22 18:01:56 +02:00
|
|
|
+ Arrays.toString(accumulator),
|
|
|
|
e.getCause());
|
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 the JVM (via reflection), and if so, we will create a special
RecordAdapter to deserialize records, using the canoncial constructor.
The ReflectionTypeAdapterFactory had to be refactored a bit to support
this. The Adapter class inside the factory is now abstract, with
concrete implementations for normal field reflection and for Records.
The common code is in the Adapter, with each implementation
deserializing values into an intermediary object.
For the FieldReflectionAdapter, the intermediary is actually the final
result, and field access is used to write to fields as before. For the
RecordAdapter the intermediary is the Object[] to pass to the Record
constructor.
* Fixed comments from @Marcono1234
Also updated so that we now use the record accessor method to read out
values from a record, so that direct field access is not necessary.
Also added some tests, that should only execute on Java versions with
record support, and be ignored for other JVMs
* Fixed additional comments from @Marcono1234
* Made Adapter in ReflectiveTypeAdapterFactory public
Fix comment from @eamonnmcmanus
2022-10-11 18:13:49 +02:00
|
|
|
}
|
2011-09-11 09:04:56 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|