Nombres de propiedades dinámicas de Jackson

Me gustaría serializar un objeto de manera que uno de los campos se denomine de manera 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 que los data campo se data.getClass.getName() a algo como data.getClass.getName() lugar de tener siempre un campo llamado data que contenga un tipo diferente según la situación.

¿Cómo podría lograr tal truco usando Jackson?

Usando un JsonSerializer personalizado.

 public class Response { private String status; private String error; @JsonProperty("p") @JsonSerialize(using = CustomSerializer.class) private Object data; // ... } public class CustomSerializer extends JsonSerializer { public void serialize(Object value, JsonGenerator jgen, SerializerProvider provider) throws IOException, JsonProcessingException { jgen.writeStartObject(); jgen.writeObjectField(value.getClass().getName(), value); jgen.writeEndObject(); } } 

Y luego, supongamos 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 se 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 envoltorio, ya que simplemente servirá como un portacartuchos. Si desea eliminarlo, tendrá que escribir un serializador personalizado para toda la clase, es decir, un JsonSerializer .

Tenía una solución más simple usando la anotación @JsonAnyGetter , y funcionó a las @JsonAnyGetter .

 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 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 un serializador personalizado.

mi propia solución

 @Data @EqualsAndHashCode @ToString @JsonSerialize(using = ElementsListBean.CustomSerializer.class) public class ElementsListBean { public ElementsListBean() { } public ElementsListBean(final String fieldName, final List elements) { this.fieldName = fieldName; this.elements = elements; } private String fieldName; private List elements; public int length() { return (this.elements != null) ? this.elements.size() : 0; } private static class CustomSerializer extends JsonSerializer { 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(); } } } } 

Puede usar la anotación JsonTypeInfo , que le dice 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 e 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 el tipo de primitiva, como una cadena o entero. No necesita esa información para primitivos de todos modos, ya que están representados de forma nativa en JSON y Jackson sabe cómo manejarlos. La ventaja añadida 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"}}}