【问题标题】:Jackson dynamic property namesJackson 动态属性名称
【发布时间】:2012-08-21 11:36:26
【问题描述】:

我想序列化一个对象,以便根据字段的类型对其中一个字段进行不同的命名。例如:

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

在这里,我希望将字段 data 序列化为 data.getClass.getName() 之类的东西,而不是总是有一个名为 data 的字段,它根据情况包含不同的类型。

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

【问题讨论】:

    标签: java json jackson


    【解决方案1】:

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

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

    【讨论】:

    • 像魅力一样工作;尤其是当您无法访问序列化程序时
    • 谁能向我解释这是如何工作的?主要是 any() 方法如何知道我们要重命名数据属性。
    • 方法的名称无关紧要,重要的是该方法返回一个 Map 并且不接受任何参数,并且应该只对一个方法进行注释。请参阅文档fasterxml.github.io/jackson-annotations/javadoc/2.6/com/… 该地图可以包含任意数量的属性。在我的示例中,我只需要一个属性,因此,使用单例映射
    • 反序列化json怎么样?例如,在我的场景中,我有一个 Map 的属性。我为这个属性赋予了动态名称,并且序列化完全按照预期进行。现在我想反序列化 json 以返回对象,但即使我创建了 JsonAnySetter,它也不起作用。有什么建议吗?
    • @nareshgoty 你用@JsonAnySetter这里有一个很好的例子:tutorialspoint.com/jackson_annotations/…
    【解决方案2】:

    使用自定义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,因为它仅用作placeholder。如果要删除它,则必须为 整个 类编写自定义序列化程序,即 JsonSerializer&lt;Response&gt;

    【讨论】:

    • 您好,我遇到了和OP一样的问题。我尝试了您的解决方案,但是我得到的是:{“status”:“Error”,“error”:“Some error”,“p”:20}。所以 @JsonProperty 注释正在工作,但不是 CustomSerializer。我正在使用 Wildfly 10 并添加了 RESTEasy jackson 提供程序作为提供的依赖项。我也尝试了这篇文章中的建议:stackoverflow.com/questions/28307646/…,但仍然无法正常工作
    【解决方案3】:

    我自己的解决方案。

    @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) 的注释
    【解决方案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;
    }
    

    但是,这不适用于原始类型,例如字符串或整数。无论如何,对于原语,您都不需要这些信息,因为它们本身以 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"}}}
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2019-02-22
      • 1970-01-01
      • 1970-01-01
      • 2016-10-21
      • 2018-04-16
      • 2017-07-28
      • 2021-11-29
      相关资源
      最近更新 更多