Nombres de propiedades dinámicas de Jackson
Frecuentes
Visto 58,309 veces
34
Me gustaría serializar un objeto de modo que uno de los campos tenga un nombre diferente según el tipo de campo. Por ejemplo:
public class Response {
private Status status;
private String error;
private Object data;
[ getters, setters ]
}
Aquí, me gustaría el campo data
para ser serializado a algo como data.getClass.getName()
en lugar de tener siempre un campo llamado data
que contiene un tipo diferente dependiendo de la situación.
¿Cómo podría lograr tal truco usando a Jackson?
4 Respuestas
39
Tuve una solución más simple usando @JsonAnyGetter
anotación, y funcionó a las mil maravillas.
import java.util.Collections;
import java.util.Map;
public class Response {
private Status status;
private String error;
@JsonIgnore
private Object data;
[getters, setters]
@JsonAnyGetter
public Map<String, Object> any() {
//add the custom name here
//use full HashMap if you need more than one property
return Collections.singletonMap(data.getClass().getName(), data);
}
}
No se necesita envoltorio, no se necesita serializador personalizado.
Respondido el 18 de diciembre de 17 a las 09:12
38
Usando una costumbre JsonSerializer
.
public class Response {
private String status;
private String error;
@JsonProperty("p")
@JsonSerialize(using = CustomSerializer.class)
private Object data;
// ...
}
public class CustomSerializer extends JsonSerializer<Object> {
public void serialize(Object value, JsonGenerator jgen, SerializerProvider provider) throws IOException, JsonProcessingException {
jgen.writeStartObject();
jgen.writeObjectField(value.getClass().getName(), value);
jgen.writeEndObject();
}
}
Y luego, suponga que desea serializar los siguientes dos objetos:
public static void main(String... args) throws Exception {
ObjectMapper mapper = new ObjectMapper();
Response r1 = new Response("Error", "Some error", 20);
System.out.println(mapper.writeValueAsString(r1));
Response r2 = new Response("Error", "Some error", "some string");
System.out.println(mapper.writeValueAsString(r2));
}
El primero imprimirá:
{"status":"Error","error":"Some error","p":{"java.lang.Integer":20}}
Y el segundo:
{"status":"Error","error":"Some error","p":{"java.lang.String":"some string"}}
he usado el nombre p
para el objeto contenedor ya que simplemente servirá como un p
porta encajes Si desea eliminarlo, debe escribir un serializador personalizado para el toda clase, es decir, un JsonSerializer<Response>
.
Respondido 27 ago 12, 00:08
Hola, tengo el mismo problema que el OP. Probé tu solución, sin embargo, lo que obtengo es: {"estado":"Error","error":"Algún error","p": 20}. Entonces, la anotación @JsonProperty funciona, pero no CustomSerializer. Estoy usando Wildfly 10 y he agregado el proveedor RESTEasy jackson como una dependencia provista. También probé lo que se sugiere en este post: stackoverflow.com/questions/28307646/…, pero sigue sin funcionar - user3362334
5
mi propia solución.
@Data
@EqualsAndHashCode
@ToString
@JsonSerialize(using = ElementsListBean.CustomSerializer.class)
public class ElementsListBean<T> {
public ElementsListBean()
{
}
public ElementsListBean(final String fieldName, final List<T> elements)
{
this.fieldName = fieldName;
this.elements = elements;
}
private String fieldName;
private List<T> elements;
public int length()
{
return (this.elements != null) ? this.elements.size() : 0;
}
private static class CustomSerializer extends JsonSerializer<Object> {
public void serialize(Object value, JsonGenerator jgen, SerializerProvider provider) throws IOException,
JsonProcessingException
{
if (value instanceof ElementsListBean) {
final ElementsListBean<?> o = (ElementsListBean<?>) value;
jgen.writeStartObject();
jgen.writeArrayFieldStart(o.getFieldName());
for (Object e : o.getElements()) {
jgen.writeObject(e);
}
jgen.writeEndArray();
jgen.writeNumberField("length", o.length());
jgen.writeEndObject();
}
}
}
}
Respondido el 06 de Septiembre de 12 a las 13:09
Tenga en cuenta que esto usa anotaciones de Lombok (projectlombok.org) - J. Dimeo
4
Puedes usar la anotación JsonTypeInfo
, que le dicen a Jackson exactamente eso y no necesita escribir un serializador personalizado. Hay varias formas de incluir esta información, pero para su pregunta específica usaría As.WRAPPER_OBJECT
y Id.CLASS
. Por ejemplo:
public static class Response {
private Status status;
private String error;
@JsonTypeInfo(include = As.WRAPPER_OBJECT, use = Id.CLASS)
private Object data;
}
Esto, sin embargo, no funcionará en tipos primitivos, como String o Integer. De todos modos, no necesita esa información para las primitivas, ya que están representadas de forma nativa en JSON y Jackson sabe cómo manejarlas. La ventaja adicional con el uso de la anotación es que obtiene la deserialización de forma gratuita, si alguna vez la necesita. Aquí hay un ejemplo:
public static void main(String[] args) throws Exception {
ObjectMapper mapper = new ObjectMapper();
Response r1 = new Response("Status", "An error", "some data");
Response r2 = new Response("Status", "An error", 10);
Response r3 = new Response("Status", "An error", new MyClass("data"));
System.out.println(mapper.writeValueAsString(r1));
System.out.println(mapper.writeValueAsString(r2));
System.out.println(mapper.writeValueAsString(r3));
}
@JsonAutoDetect(fieldVisibility=Visibility.ANY)
public static class MyClass{
private String data;
public MyClass(String data) {
this.data = data;
}
}
y el resultado:
{"status":"Status","error":"An error","data":"some data"}
{"status":"Status","error":"An error","data":10}
{"status":"Status","error":"An error","data":{"some.package.MyClass":{"data":"data"}}}
respondido 23 nov., 16:07
No es la respuesta que estás buscando? Examinar otras preguntas etiquetadas java json jackson or haz tu propia pregunta.
Funciona de maravilla; especialmente cuando no tiene acceso al serializador - Anatoly Yakimchuk
¿Alguien puede explicarme cómo funciona esto? Principalmente, ¿cómo sabe el método any() que queremos cambiar el nombre del atributo de datos? - smithzo622
El nombre del método es irrelevante, lo que importa es que el método devuelve un Map y no admite argumentos, y solo un método debe anotarse con tal. Ver documentación rapidxml.github.io/jackson-annotations/javadoc/2.6/com/… El mapa puede contener cualquier número de propiedades. En el caso de mi ejemplo, solo necesitaba una propiedad, por lo tanto, el uso del mapa Singleton: tlogbon
¿Qué hay de deserializar el json de nuevo? Por ejemplo, en mi escenario, tengo un atributo de Mapa . Le di un nombre dinámico a este atributo y la serialización funciona exactamente como se esperaba. Ahora quiero deserializar el json para objetar, pero incluso creé JsonAnySetter, no funcionó. ¿Alguna recomendación? - naresh goty
@nareshgoty Tú usas
@JsonAnySetter
hay un buen ejemplo aquí: tutorialspoint.com/jackson_annotations/… - tlogbon