Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Adding support for using getter/setter methods based on user choice #232 #1094

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 9 additions & 2 deletions gson/src/main/java/com/google/gson/Gson.java
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,7 @@ public final class Gson {
static final boolean DEFAULT_SERIALIZE_NULLS = false;
static final boolean DEFAULT_COMPLEX_MAP_KEYS = false;
static final boolean DEFAULT_SPECIALIZE_FLOAT_VALUES = false;
static final boolean DEFAULT_USE_GETTER_SETTER = false;

private static final TypeToken<?> NULL_KEY_SURROGATE = TypeToken.get(Object.class);
private static final String JSON_NON_EXECUTABLE_PREFIX = ")]}'\n";
Expand Down Expand Up @@ -135,6 +136,7 @@ public final class Gson {
private final boolean prettyPrinting;
private final boolean lenient;
private final JsonAdapterAnnotationTypeAdapterFactory jsonAdapterFactory;
private final boolean useGetterSetter;

/**
* Constructs a Gson object with default configuration. The default configuration has the
Expand Down Expand Up @@ -175,15 +177,15 @@ public Gson() {
Collections.<Type, InstanceCreator<?>>emptyMap(), DEFAULT_SERIALIZE_NULLS,
DEFAULT_COMPLEX_MAP_KEYS, DEFAULT_JSON_NON_EXECUTABLE, DEFAULT_ESCAPE_HTML,
DEFAULT_PRETTY_PRINT, DEFAULT_LENIENT, DEFAULT_SPECIALIZE_FLOAT_VALUES,
LongSerializationPolicy.DEFAULT, Collections.<TypeAdapterFactory>emptyList());
LongSerializationPolicy.DEFAULT, Collections.<TypeAdapterFactory>emptyList(), DEFAULT_USE_GETTER_SETTER);
}

Gson(final Excluder excluder, final FieldNamingStrategy fieldNamingStrategy,
final Map<Type, InstanceCreator<?>> instanceCreators, boolean serializeNulls,
boolean complexMapKeySerialization, boolean generateNonExecutableGson, boolean htmlSafe,
boolean prettyPrinting, boolean lenient, boolean serializeSpecialFloatingPointValues,
LongSerializationPolicy longSerializationPolicy,
List<TypeAdapterFactory> typeAdapterFactories) {
List<TypeAdapterFactory> typeAdapterFactories, boolean useGetterSetter) {
this.constructorConstructor = new ConstructorConstructor(instanceCreators);
this.excluder = excluder;
this.fieldNamingStrategy = fieldNamingStrategy;
Expand All @@ -192,6 +194,7 @@ public Gson() {
this.htmlSafe = htmlSafe;
this.prettyPrinting = prettyPrinting;
this.lenient = lenient;
this.useGetterSetter = useGetterSetter;

List<TypeAdapterFactory> factories = new ArrayList<TypeAdapterFactory>();

Expand Down Expand Up @@ -271,6 +274,10 @@ public boolean htmlSafe() {
return htmlSafe;
}

public boolean useGetterSetter() {
return useGetterSetter;
}

private TypeAdapter<Number> doubleAdapter(boolean serializeSpecialFloatingPointValues) {
if (serializeSpecialFloatingPointValues) {
return TypeAdapters.DOUBLE;
Expand Down
17 changes: 16 additions & 1 deletion gson/src/main/java/com/google/gson/GsonBuilder.java
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@
import static com.google.gson.Gson.DEFAULT_PRETTY_PRINT;
import static com.google.gson.Gson.DEFAULT_SERIALIZE_NULLS;
import static com.google.gson.Gson.DEFAULT_SPECIALIZE_FLOAT_VALUES;
import static com.google.gson.Gson.DEFAULT_USE_GETTER_SETTER;

/**
* <p>Use this builder to construct a {@link Gson} instance when you need to set configuration
Expand Down Expand Up @@ -94,6 +95,7 @@ public final class GsonBuilder {
private boolean prettyPrinting = DEFAULT_PRETTY_PRINT;
private boolean generateNonExecutableJson = DEFAULT_JSON_NON_EXECUTABLE;
private boolean lenient = DEFAULT_LENIENT;
private boolean useGetterSetter = DEFAULT_USE_GETTER_SETTER;

/**
* Creates a GsonBuilder instance that can be used to build Gson with various configuration
Expand Down Expand Up @@ -552,6 +554,19 @@ public GsonBuilder serializeSpecialFloatingPointValues() {
return this;
}

/**
* By default, Gson uses reflection to get/set values of the fields. Use this option to configure
* Gson to use getter/setter methods whenever possible. If no method is found,
* it will fallback to using reflection.
*
* @return a reference to this {@code GsonBuilder} object to fulfill the "Builder" pattern
* @since 1.8
*/
public GsonBuilder useGetterSetter() {
this.useGetterSetter = true;
return this;
}

/**
* Creates a {@link Gson} instance based on the current configuration. This method is free of
* side-effects to this {@code GsonBuilder} instance and hence can be called multiple times.
Expand All @@ -569,7 +584,7 @@ public Gson create() {
return new Gson(excluder, fieldNamingPolicy, instanceCreators,
serializeNulls, complexMapKeySerialization,
generateNonExecutableJson, escapeHtmlChars, prettyPrinting, lenient,
serializeSpecialFloatingPointValues, longSerializationPolicy, factories);
serializeSpecialFloatingPointValues, longSerializationPolicy, factories, useGetterSetter);
}

@SuppressWarnings("unchecked")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,8 @@
import com.google.gson.stream.JsonWriter;
import java.io.IOException;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Type;
import java.util.ArrayList;
import java.util.Collections;
Expand Down Expand Up @@ -119,7 +121,42 @@ private ReflectiveTypeAdapterFactory.BoundField createBoundField(
@SuppressWarnings({"unchecked", "rawtypes"}) // the type adapter and field type always agree
@Override void write(JsonWriter writer, Object value)
throws IOException, IllegalAccessException {
Object fieldValue = field.get(value);
Object fieldValue;

if(context.useGetterSetter()) {
String fieldName = field.getName();
String getterPrefix = field.getType() == boolean.class || field.getType() == Boolean.class ? "is" : "get";
String getterName = getterPrefix + fieldName.substring(0, 1).toUpperCase() + fieldName.substring(1);

try {
Method method = value.getClass().getMethod(getterName);
fieldValue = method.invoke(value);

} catch (NoSuchMethodException ignored) {
/*
Getting field via reflection if no setter method is found in the class for that field
*/
fieldValue = field.get(value);

} catch (InvocationTargetException ignored) {
/*
TODO : Needs to be revisited
If use of getter is enabled & Gson is unable to call getter, then throw error or just use reflection?
Since earlier Gson version worked in all cases, using reflection to get field value for now.
*/
fieldValue = field.get(value);
} catch (IllegalAccessException ignored) {
/*
TODO : Needs to be revisited
If use of getter is enabled & Gson is unable to call getter, then throw error or just use reflection?
Since earlier Gson version worked in all cases, using reflection to get field value for now.
*/
fieldValue = field.get(value);
}
} else {
fieldValue = field.get(value);
}

TypeAdapter t = jsonAdapterPresent ? typeAdapter
: new TypeAdapterRuntimeTypeWrapper(context, typeAdapter, fieldType.getType());
t.write(writer, fieldValue);
Expand All @@ -128,7 +165,39 @@ private ReflectiveTypeAdapterFactory.BoundField createBoundField(
throws IOException, IllegalAccessException {
Object fieldValue = typeAdapter.read(reader);
if (fieldValue != null || !isPrimitive) {
field.set(value, fieldValue);

if (context.useGetterSetter()) {
String fieldName = field.getName();
String setterName = "set" + fieldName.substring(0, 1).toUpperCase() + fieldName.substring(1);

try {
Method method = value.getClass().getMethod(setterName, field.getType());
method.invoke(value, fieldValue);

} catch (NoSuchMethodException ignored) {
/*
Setting field via reflection if no setter method is found in the class for that field
*/
field.set(value, fieldValue);

} catch (InvocationTargetException ignored) {
/*
TODO : Needs to be revisited
If use of setter is enabled & Gson is unable to call setter, then throw error or just use reflection?
Since earlier Gson version worked in all cases, using reflection to set field value for now.
*/
field.set(value, fieldValue);
} catch (IllegalAccessException ignored) {
/*
TODO : Needs to be revisited
If use of setter is enabled & Gson is unable to call setter, then throw error or just use reflection?
Since earlier Gson version worked in all cases, using reflection to set field value for now.
*/
field.set(value, fieldValue);
}
} else {
field.set(value, fieldValue);
}
}
}
@Override public boolean writeField(Object value) throws IOException, IllegalAccessException {
Expand Down
3 changes: 2 additions & 1 deletion gson/src/test/java/com/google/gson/GsonTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -44,11 +44,12 @@ public void testOverridesDefaultExcluder() {
Gson gson = new Gson(CUSTOM_EXCLUDER, CUSTOM_FIELD_NAMING_STRATEGY,
new HashMap<Type, InstanceCreator<?>>(), true, false, true, false,
true, true, false, LongSerializationPolicy.DEFAULT,
new ArrayList<TypeAdapterFactory>());
new ArrayList<TypeAdapterFactory>(), true);

assertEquals(CUSTOM_EXCLUDER, gson.excluder());
assertEquals(CUSTOM_FIELD_NAMING_STRATEGY, gson.fieldNamingStrategy());
assertEquals(true, gson.serializeNulls());
assertEquals(false, gson.htmlSafe());
assertEquals(true, gson.useGetterSetter());
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
package com.google.gson.functional;

import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import junit.framework.TestCase;

/**
* Functional Test exercising serialization/deserialization using getter/setter methods.
*
* @author Raj Srivastava
*/
public class UseGetterSetterTest extends TestCase {

private static final int INITIAL_INT = 100;
private static final int GETTER_OFFSET = 20;
private static final int SETTER_OFFSET = 40;
private static final String INITIAL_STRING = "initial";
private static final String GETTER_SUFFIX = "-g";
private static final String SETTER_SUFFIX = "-s";

private Gson gson;

@Override
protected void setUp() throws Exception {
super.setUp();
gson = new GsonBuilder().useGetterSetter().create();
}

public void testGetterSetterUse() throws Exception {
ClassWithGetterSetter request = new ClassWithGetterSetter(INITIAL_INT, INITIAL_STRING);
String json = gson.toJson(request);

ClassWithGetterSetter response = gson.fromJson(json, ClassWithGetterSetter.class);
assertEquals(response.primitiveField, INITIAL_INT + GETTER_OFFSET + SETTER_OFFSET);
assertEquals(response.nonPrimitiveField, INITIAL_STRING + GETTER_SUFFIX + SETTER_SUFFIX);
}

public static class ClassWithGetterSetter {
int primitiveField;
String nonPrimitiveField;

public ClassWithGetterSetter(int primitiveField, String nonPrimitiveField) {
this.primitiveField = primitiveField;
this.nonPrimitiveField = nonPrimitiveField;
}

public int getPrimitiveField() {
return primitiveField + GETTER_OFFSET;
}

public void setPrimitiveField(int primitiveField) {
this.primitiveField = primitiveField + SETTER_OFFSET;
}

public String getNonPrimitiveField() {
return nonPrimitiveField + GETTER_SUFFIX;
}

public void setNonPrimitiveField(String nonPrimitiveField) {
this.nonPrimitiveField = nonPrimitiveField + SETTER_SUFFIX;
}
}
}