De manera predeterminada, Gson usa campos como base para su serialización. ¿Hay alguna manera de hacer que use accessors en su lugar?¿Cómo puedo hacer que Gson use accesadores en lugar de campos?
Respuesta
Los desarrolladores de Gson say que nunca se sintieron influenciados por las solicitudes para agregar esta característica y estaban preocupados por oscurecer la API para agregar soporte para esto.
Una forma de añadir esta funcionalidad es mediante el uso de un TypeAdapter (Me disculpo por el código retorcido pero esto demuestra el principio):
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.");
}
}
Puede registrar esto como un adaptador de tipo normal para un tipo dado o a través de un TypeAdapterfactory - posiblemente la comprobación de la presencia de una anotación de tiempo de ejecución:
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;
}
Esto se puede especificar como normal cuando la creación de la instancia de GSON:
new GsonBuilder().registerTypeAdapterFactory(new TypeFactory()).create();
Brillante ejemplo.Si realmente avanzas con esto, probablemente puedas hacer que sea un poco más eficiente al buscar y almacenar en caché los métodos en TypeAdapterFactory.create(). –
@ plasma147 ¡Gracias por esto! Aquí está la anotación UseAccessor: '@Retention (RetentionPolicy.RUNTIME) @Target (ElementType.TYPE) public @interface UseAccessor { }' –
¡Gracias! Dios, esto me ahorró muchos más problemas. ¿Por qué no hay ningún ejemplo oficial de lo que escribiste? Y gracias a Smoky por el UseAccessor-annotation, bastante nuevo en Java, me habría causado incluso más problemas al tratar de descubrir la parte de la anotación. – Whyser
Nota: Soy el EclipseLink JAXB (MOXy) de plomo y un miembro del grupo de expertos JAXB (JSR-222).
Si no puede hacer que Gson haga lo que desea, a continuación se muestra cómo puede lograr esto utilizando el enlace JSON nativo de MOXy. MOXy, como cualquier implementación de JAXB, usará acceso de propiedad (pública) de forma predeterminada. Puede configurar el acceso al campo usando @XmlAccessorType(XmlAccessType.FIELD)
. A continuación se muestra un ejemplo:
cliente
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;
}
}
Dirección
package forum11385214;
public class Address {
private String foo;
public String getStreet() {
return foo;
}
public void setStreet(String street) {
this.foo = street;
}
}
jaxb.properties
Para configurar moxy como su proveedor de JAXB es necesario agregar una archivo llamado jaxb.properties
en el mismo paquete que su modelo de dominio con la siguiente entrada (consulte: http://blog.bdoughan.com/2011/05/specifying-eclipselink-moxy-as-your.html).
javax.xml.bind.context.factory=org.eclipse.persistence.jaxb.JAXBContextFactory
demostración
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/salida
{
"name" : "Jane Doe",
"address" : {
"street" : "1 Any Street"
}
}
Para más información
- 1. ¿Cómo hacer que Jersey use SLF4J en lugar de JUL?
- 2. ¿Cómo puedo hacer que el cliente web use cookies?
- 3. ¿Cómo puedo hacer que StackPanel use una ItemTemplate?
- 4. ¿Cómo hacer que el control SWT Browser use Mozilla en lugar de IE en Windows?
- 5. ¿Cómo hacer que HttpClient use Kerberos?
- 6. cómo especificar que jackson solo use campos: preferiblemente globalmente
- 7. Gson uso de fuerza int en lugar de la doble
- 8. Use Gson para serializar un POJO
- 9. Cómo analizar los campos dinámicos JSON con GSON?
- 10. Use outer en lugar de expand.grid
- 11. Hacer que GNU use un compilador diferente
- 12. Hacer que Qt Creator use MinGW
- 13. Use DataMapper en lugar de ActiveRecord
- 14. ¿Cómo hacer que LogonUser no use credenciales en caché?
- 15. ¿Cómo hacer que boost.build use una instalación de compilación específica?
- 16. ¿Cómo hacer que un WPF TextBox use caracteres de contraseña?
- 17. No puedo hacer git diff use opendiff
- 18. ¿Cómo puedo hacer que Hibernate ponga comentarios en las tablas/campos que crea?
- 19. GWT - ¿Hacer que CellTable Cell use HTML?
- 20. ¿Cómo hacer que gcc use march = native como predeterminado?
- 21. ¿Cómo hacer que Eclipse use JDK8 para compilar un proyecto?
- 22. ¿Cómo hacer que Emacs use mi archivo .bashrc?
- 23. ¿Cómo hacer que NSTextField use la subclase personalizada de NSTextFieldCell?
- 24. ¿Cómo hacer que R use todos los procesadores?
- 25. GSON convierte a LinkedHashMap en lugar de mi objeto
- 26. ¿Cómo puedo hacer que FlashBuilder use un prefijo de espacio de nombre personalizado?
- 27. Jenkins: ¿Cómo puedo hacer que un trabajo enviado de forma remota use valores de parámetros predeterminados?
- 28. ¿Cómo puedo hacer que AJAX-y muestre/oculte condicional los campos de formulario en Rails?
- 29. ¿Cómo puedo hacer que named_scope en Rails devuelva un valor en lugar de una matriz?
- 30. ¿Cómo puedo hacer que mi script de Perl use múltiples núcleos para procesos secundarios?
¿Cómo es esta ambigua, imprecisa, incompleta, demasiado amplia, o retórica? Cubre un caso de uso muy común. – plasma147