Nombres de propiedades dinámicas de Jackson

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?

preguntado el 26 de agosto de 12 a las 22:08

4 Respuestas

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

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

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 pporta 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

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

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 or haz tu propia pregunta.