java - deserialize - Nombres dinámicos de propiedades de Jackson
json serialize java (4)
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();
}
}
}
}
Me gustaría serializar un objeto tal que uno de los campos se nombre de forma diferente según el tipo de campo. Por ejemplo:
public class Response {
private Status status;
private String error;
private Object data;
[ getters, setters ]
}
En este caso, me gustaría que los data
campo se serializaran a algo como data.getClass.getName()
lugar de tener siempre un campo llamado data
que contiene un tipo diferente según la situación.
¿Cómo podría lograr tal truco usando Jackson?
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;
}
Sin embargo, esto no funcionará en un tipo primitivo, como una cadena o un entero. De todos modos, no necesita esa información para los primitivos, 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 usted obtiene 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"}}}
Tuve una solución más simple utilizando la anotación @JsonAnyGetter
, y funcionó a la @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<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 un serializador personalizado.
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<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 envoltorio, ya que simplemente servirá como un p
laceholder. Si desea eliminarlo, tendría que escribir un serializador personalizado para toda la clase, es decir, un JsonSerializer<Response>
.