Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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

Tags:

java

json

gson

By default Gson uses fields as a basis for it's serialization. Is there a way to get it to use accessors instead?

like image 966
plasma147 Avatar asked Jul 08 '12 17:07

plasma147


People also ask

Does Gson ignore transient fields?

By default, GSON excludes transient and static fields from the serialization/deserialization process.

Does Gson ignore extra fields?

As you can see, Gson will ignore the unknown fields and simply match the fields that it's able to.

Does Gson work with private fields?

In this example you'll see how the Gson library handles the object fields. For object fields to be serialized into JSON string it doesn't need to use any annotations, it can even read private fields.

Does Gson use getters and setters?

There are no files selected for viewing. * Gson to use getter/setter methods whenever possible. If no method is found, * it will fallback to using reflection.


2 Answers

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();
like image 165
plasma147 Avatar answered Sep 30 '22 03:09

plasma147


Note: I'm the EclipseLink JAXB (MOXy) lead and a member of the JAXB (JSR-222) expert group.

If you can't get Gson to do what you want, below is how you can accomplish this using MOXy's native JSON binding. MOXy like any JAXB implementation will use property (public) access by default. You can configure field access using @XmlAccessorType(XmlAccessType.FIELD). Below is an example:

Customer

package forum11385214;

public class Customer {

    private String foo;
    private Address bar;

    public String getName() {
        return foo;
    }

    public void setName(String name) {
        this.foo = name;
    }

    public Address getAddress() {
        return bar;
    }

    public void setAddress(Address address) {
        this.bar = address;
    }

}

Address

package forum11385214;

public class Address {

    private String foo;

    public String getStreet() {
        return foo;
    }

    public void setStreet(String street) {
        this.foo = street;
    }

}

jaxb.properties

To configure MOXy as your JAXB provider you need to add a file called jaxb.properties in the same package as your domain model with the following entry (see: http://blog.bdoughan.com/2011/05/specifying-eclipselink-moxy-as-your.html).

javax.xml.bind.context.factory=org.eclipse.persistence.jaxb.JAXBContextFactory

Demo

package forum11385214;

import java.util.*;
import javax.xml.bind.*;
import javax.xml.transform.stream.StreamSource;
import org.eclipse.persistence.jaxb.JAXBContextProperties;

public class Demo {

    public static void main(String[] args) throws Exception {
        Map<String, Object> properties = new HashMap<String, Object>(2);
        properties.put(JAXBContextProperties.MEDIA_TYPE, "application/json");
        properties.put(JAXBContextProperties.JSON_INCLUDE_ROOT, false);
        JAXBContext jc = JAXBContext.newInstance(new Class[] {Customer.class}, properties);

        Unmarshaller unmarshaller = jc.createUnmarshaller();
        StreamSource json = new StreamSource("src/forum11385214/input.json");
        Customer customer = (Customer) unmarshaller.unmarshal(json, Customer.class).getValue();

        Marshaller marshaller = jc.createMarshaller();
        marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true);
        marshaller.marshal(customer, System.out);
    }

}

input.json/Output

{
    "name" : "Jane Doe",
    "address" : {
        "street" : "1 Any Street"
    }
}

For More Information

  • http://blog.bdoughan.com/2011/08/json-binding-with-eclipselink-moxy.html
  • http://blog.bdoughan.com/2011/06/using-jaxbs-xmlaccessortype-to.html
  • http://blog.bdoughan.com/2012/04/jaxb-and-unmapped-properties.html
like image 36
bdoughan Avatar answered Sep 30 '22 04:09

bdoughan