Why Does Gson Use Fields and Not Getters/Setters

Why does GSON use fields and not getters/setters?

Generally speaking when you serialize/deserialize an object, you are doing so to end up with an exact copy of the state of the object; As such, you generally want to circumvent the encapsulation normally desired in an OO design. If you do not circumvent the encapsulation, it may not be possible to end up with an object that has the exact same state after deserialization as it had prior to serialization. Additionally, consider the case where you do not want to provide a setter for a particular property. How should serialization/deserialization act if you are working through the getters and setters?

How can I get Gson to use accessors rather than fields?

The developers of Gson say that they never felt swayed by the requests to add this feature and they were worried about murkying up the api to add support for this.

One way of adding this functionality is by using a TypeAdapter (I apologize for the gnarly code but this demonstrates the principle):

import java.io.IOException;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;

import com.google.common.base.CaseFormat;
import com.google.gson.Gson;
import com.google.gson.TypeAdapter;
import com.google.gson.reflect.TypeToken;
import com.google.gson.stream.JsonReader;
import com.google.gson.stream.JsonWriter;

public class AccessorBasedTypeAdaptor<T> extends TypeAdapter<T> {

private Gson gson;

public AccessorBasedTypeAdaptor(Gson gson) {
this.gson = gson;
}

@SuppressWarnings("unchecked")
@Override
public void write(JsonWriter out, T value) throws IOException {
out.beginObject();
for (Method method : value.getClass().getMethods()) {
boolean nonBooleanAccessor = method.getName().startsWith("get");
boolean booleanAccessor = method.getName().startsWith("is");
if ((nonBooleanAccessor || booleanAccessor) && !method.getName().equals("getClass") && method.getParameterTypes().length == 0) {
try {
String name = method.getName().substring(nonBooleanAccessor ? 3 : 2);
name = CaseFormat.UPPER_CAMEL.to(CaseFormat.LOWER_CAMEL, name);
Object returnValue = method.invoke(value);
if(returnValue != null) {
TypeToken<?> token = TypeToken.get(returnValue.getClass());
TypeAdapter adapter = gson.getAdapter(token);
out.name(name);
adapter.write(out, returnValue);
}
} catch (Exception e) {
throw new ConfigurationException("problem writing json: ", e);
}
}
}
out.endObject();
}

@Override
public T read(JsonReader in) throws IOException {
throw new UnsupportedOperationException("Only supports writes.");
}
}

You can register this as a normal type adapter for a given type or through a TypeAdapterfactory - possibly checking for the presence of a runtime annotation:

public class TypeFactory implements TypeAdapterFactory {

@SuppressWarnings("unchecked")
public <T> TypeAdapter<T> create(final Gson gson, final TypeToken<T> type) {
Class<? super T> t = type.getRawType();
if(t.isAnnotationPresent(UseAccessor.class)) {
return (TypeAdapter<T>) new AccessorBasedTypeAdaptor(gson);
}
return null;
}

This can be specified as normal when creating your gson instance:

new GsonBuilder().registerTypeAdapterFactory(new TypeFactory()).create();

Is possible to use setters when Gson deserializes a JSON?

I implemented a JsonDeserializer<String> and registered it on GsonBuilder. So, to all String fields received, Gson will use my StringGsonTypeAdapter to deserialize the value.

Below is my code:

import static net.hugonardo.java.commons.text.StringUtils.normalizeSpace;
import static net.hugonardo.java.commons.text.StringUtils.trimToNull;

final class StringGsonTypeAdapter implements JsonDeserializer<String> {

private static final StringGsonTypeAdapter INSTANCE = new StringGsonTypeAdapter();

static StringGsonTypeAdapter instance() {
return INSTANCE;
}

@Override
public String deserialize(JsonElement jsonElement, Type type,
JsonDeserializationContext jsonDeserializationContext) throws JsonParseException {
return normalizeSpace(trimToNull(jsonElement.getAsString()));
}
}

...and my GsonBuilder:

Gson gson = new GsonBuilder()
.registerTypeAdapter(String.class, StringGsonTypeAdapter.instance())
.create())

Force GSON to use Constructor containing Setters or Setters in general, instead of Fields

I've looked at Gson's User Guide and it looks like it isn't implemented yet.. Supporting Getters and Setters is placed on their TODO/issues-list (issue 232).

I've got a temporarily (inefficient and ugly solution):

public void jsonToProducts(String json){
ArrayList<Product> products = null;
if(json != null && json.length() > 0){
try{
Type listType = new TypeToken<ArrayList<Product>>(){}.getType();
products = new Gson().fromJson(json, listType);
}
catch(JsonParseException ex){
ex.printStackTrace();
}
}
// TODO: Replace when Gson supports Setters or Parameterized Constructors
if(products != null && products.size() > 0){
for(Product p : products){
p.setProductId(p.getProductId());
p.setName(p.getName());
}
}

setProducts(products);
}

I will accept this as answer for now. If anyone knows a better way of handling Gson's lack of Setter-support I would appreciate it. (Especially since my Product-List is around 1250 Products large, so looping through it resetting all fields isn't that good for performance in a mobile app. Luckily this only happens once at start-up..)

Difference between Getter/Setter and this other Data object Class

From documentation:

Using fields vs getters to indicate Json elements

Some Json libraries use the getters of a type to deduce the Json
elements. We chose to use all fields (up the inheritance hierarchy)
that are not transient, static, or synthetic. We did this because not
all classes are written with suitably named getters. Moreover, getXXX
or isXXX might be semantic rather than indicating properties.

However, there are good arguments to support properties as well. We
intend to enhance Gson in a latter version to support properties as an
alternate mapping for indicating Json fields. For now, Gson is
fields-based.

As you can read, Gson is fields-based and in your case there is no difference between fields in two versions of the same class.

If you use MyObject only for deserialisation purpose and later you convert it to some business model you can stay with shorter version. But in case, MyObject is propagated over many layers it could introduce inconsistency with other classes which uses getters/setters.

Maybe Lombok library would be the best to use for you and your colleague?

  • Why does GSON use fields and not getters/setters?

How does gson maps JSON keys with the fields in Java classes while deserializing the JSON

  1. I'm note sure about this one but i think the case only matters after the first character because you normally don't start the name of field with an upper-case character.
    Yes GSON will automatically map the fields.

  2. Yes GSON does not need getter/setter
    (https://stackoverflow.com/a/6203975/4622620)

  3. Yes GSON can handle private fields because it uses reflections (https://stackoverflow.com/a/28927525/4622620)

Does Google's GSON use constructors?

Libraries like GSON, Jackson or the Java Persistence API (JPA) generally use a no-argument (default) construtor to instantiate an object and set its fields via reflection. In newer versions of GSON, you do not even have to declare a default constructor anymore, see here.

If you have to call a specific constructor in GSON, you can implement a custom JsonDeserializer, like already mentioned here.

In other libraries like Jackson, you can define the deserialization on methods, instead of fields (like GSON does), which allows you to substitute the missing specialized constructor call.

serializing with gson and back does not work with generic types

It is due to type erasure of generics. You need to use TokenType to retrieve the type information at runtime.

val fromJson = gson1.fromJson<WrapperDto<Person>>(toJson, object: TypeToken<WrapperDto<Person>>() {}.type)

You may also create an extension function like this:

inline fun <reified T> fromJson(json: String): T = Gson().fromJson<T>(json, object: TypeToken<T>() {}.type)

So, you can call fromJson this way:

val fromJson = fromJson<WrapperDto<Person>>(toJson)
//Or
val fromJson: WrapperDto<Person> = fromJson(toJson) //Type inferred


Related Topics



Leave a reply



Submit