Jackson动态属性名称

35

我想要序列化一个对象,使得其中一个字段的名称根据该字段的类型而有所不同。例如:

public class Response {
    private Status status;
    private String error;
    private Object data;
        [ getters, setters ]
    }

我希望字段data被序列化成类名而不是一个包含根据情况而异的不同类型的字段data

我该如何使用Jackson实现这样的技巧?

5个回答

44

我使用@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);
    }
}

不需要包装器,也不需要自定义序列化程序。


运行得非常好,特别是当您无法访问序列化程序时。 - Anatoly Yakimchuk
有人能解释一下这是如何工作的吗?主要是任何()方法如何知道我们想要重命名数据属性。 - smithzo622
方法的名称并不重要,重要的是该方法返回一个Map且不带任何参数,并且只有一个方法应该被注释为这样。请参阅文档 https://fasterxml.github.io/jackson-annotations/javadoc/2.6/com/fasterxml/jackson/annotation/JsonAnyGetter.html该Map可以包含任意数量的属性。在我的示例中,我只需要一个属性,因此使用了Singleton map。 - tlogbon
反序列化JSON怎么样?例如,在我的场景中,我有一个Map<String,CustomObjectType>属性。我为这个属性赋予了动态名称,序列化正如预期的那样进行。现在我想将JSON反序列化回对象,但即使我创建了JsonAnySetter,它也没有起作用。有什么建议吗? - naresh goty
@nareshgoty 你使用了 @JsonAnySetter,这里有一个很好的例子:https://www.tutorialspoint.com/jackson_annotations/jackson_annotations_jsonanysetter.htm - tlogbon
显示剩余2条评论

38

使用自定义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();
  }
}

假设您想序列化以下两个对象:

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));
}
第一个将会打印:
{"status":"Error","error":"Some error","p":{"java.lang.Integer":20}}

还有第二个:

{"status":"Error","error":"Some error","p":{"java.lang.String":"some string"}}

我使用了名称为p的包装对象,因为它仅仅充当一个占位符。如果你想要移除它,你需要为整个类编写自定义序列化器,即JsonSerializer<Response>


你好,我和楼主有同样的问题。我尝试了你的解决方案,但是我得到的是:{"status":"Error","error":"Some error","p": 20}。所以@JsonProperty注释起作用了,但CustomSerializer没有。我正在使用Wildfly 10,并将RESTEasy jackson提供程序添加为提供的依赖项。我还尝试了这篇文章中建议的方法:https://dev59.com/R14c5IYBdhLWcg3wHHGY,但仍然不起作用。 - user3362334

6

我的自己的解决方案。

@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();
            }
        }
    }
}

请注意,这里使用了 Lombok(projectlombok.org)的注解。 - J. Dimeo

4
您可以使用注解JsonTypeInfo,告诉Jackson确切的信息,您无需编写自定义序列化程序。有多种方法包含此信息,但对于您的具体问题,您将使用As.WRAPPER_OBJECTId.CLASS。例如:
public static class Response {
    private Status status;
    private String error;
    @JsonTypeInfo(include = As.WRAPPER_OBJECT, use = Id.CLASS)
    private Object data;
}

然而,这种方法对于原始类型(如String或Integer)是无效的。对于原始类型,您也不需要该信息,因为它们在JSON中具有本机表示,并且Jackson知道如何处理它们。使用注释的附加好处是,如果您需要反序列化,则可以免费获得。以下是一个示例:

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;
    }
}

结果如下:

{"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"}}}

0

根据@tlogbon的回复, 这是我包装具有特定/动态字段名称的项目列表的解决方案

public class ListResource<T> {

    @JsonIgnore
    private List<T> items;

    @JsonIgnore
    private String fieldName;

    public ListResource(String fieldName, List<T> items) {
        this.items = items;
        this.fieldName = fieldName;
    }

    @JsonAnyGetter
    public Map<String, List<T>> getMap() {
        return Collections.singletonMap(fieldName, items);
    }

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接