【问题标题】:Use class name as root key for JSON Jackson serialization使用类名作为 JSON Jackson 序列化的根键
【发布时间】:2011-01-26 23:46:36
【问题描述】:

假设我有一个 pojo:

import org.codehaus.jackson.map.*;

public class MyPojo {
    int id;
    public int getId()
    { return this.id; }

    public void setId(int id)
    { this.id = id; }

    public static void main(String[] args) throws Exception {
        MyPojo mp = new MyPojo();
        mp.setId(4);
        ObjectMapper mapper = new ObjectMapper();
        mapper.configure(SerializationConfig.Feature.WRAP_ROOT_VALUE, true);
        System.out.println(mapper.getSerializationConfig().isEnabled(SerializationConfig.Feature.WRAP_ROOT_VALUE));
        System.out.println(mapper.writeValueAsString(mp));
    }
}

当我使用 Jackson ObjectMapper 进行序列化时,我得到了

true
{"id":4}

但我想要

true
{"MyPojo":{"id":4}}

我已经到处搜索了,Jacksons 的文档确实杂乱无章,而且大多已经过时了。

【问题讨论】:

    标签: java json serialization jackson


    【解决方案1】:

    通过在类级别添加杰克逊注解@JsonTypeInfo,您可以获得预期的输出。我只是在你的课程中添加了无变化。

    package com.test.jackson;
    
    import org.codehaus.jackson.map.ObjectMapper;
    import org.codehaus.jackson.map.SerializationConfig;
    
    import com.fasterxml.jackson.annotation.JsonTypeInfo;
    import com.fasterxml.jackson.annotation.JsonTypeInfo.As;
    import com.fasterxml.jackson.annotation.JsonTypeInfo.Id;
    
    @JsonTypeInfo(include=As.WRAPPER_OBJECT, use=Id.NAME)
    public class MyPojo {
        // Remain same as you have
    }
    

    输出:

    {
        "MyPojo": {
            "id": 4
        }
    }
    

    【讨论】:

    • 完美。为我工作。有没有办法将描述符更改为不是类名?喜欢mypojo 而不是MyPojo
    • @JsonTypeName("foo") @JsonTypeInfo(include = JsonTypeInfo.As.WRAPPER_OBJECT ,use = JsonTypeInfo.Id.NAME)
    【解决方案2】:

    我没有使用jackson,但是搜索我发现这个配置似乎是你想要的:WRAP_ROOT_VALUE

    可以启用将根值(通常是 JSON 对象,但可以是任何类型)包装在单个属性 JSON 对象中的功能,其中键作为“根名称”,由注释内省器确定(尤其是对于 JAXB使用@XmlRootElement.name)或回退(非限定类名)。该功能主要用于 JAXB 兼容性。

    默认设置为false,意思是root 值未包装。

    这样就可以配置mapper了:

    objectMapper.configure(SerializationConfig.Feature.WRAP_ROOT_VALUE, true);
    

    希望对你有帮助……

    【讨论】:

    • 欢迎来到 Jacksons 的古怪世界,其中记录了实际上不起作用的功能。不过感谢您的帮助。
    • FWIW,这将是预期的实现(如预期的那样)。包含特性枚举是不幸的,因为所有其他特性都已实现(AFAIK),在实现之前添加它们当然没有意义。如果您希望它实施,您可以(除了贡献实施)在 Jira 中投票支持它和/或在邮件列表中游说。
    • 另请参阅JACKSON-630,这导致在 Jackson 1.9 中添加了 @JsonRootName 注释,用于配置根名称。 WRAP_ROOT_VALUE 仍然必须启用才能使此注释生效。
    • 我认为跳过这么多圈来做一些像在需要的地方启用@JsonRootName这样简单的事情是荒谬的。
    • objectMapper.enable(SerializationFeature.WRAP_ROOT_VALUE); 更新版本。
    【解决方案3】:

    以下是实现此目的的方法

    Map<String, MyPojo> singletonMap = Collections.singletonMap("mypojo", mp);
    System.out.println(mapper.writeValueAsString(singletonMap));
    

    输出 {“mypojo”:{“id”:4}}

    这里的好处是我们可以为 json 对象的根键提供我们的名称。通过上面的代码,mypojo 将是根键。当我们使用像 Mustache.js 这样的 java 脚本模板来迭代 json 对象时,这种方法将是最有用的

    【讨论】:

    • 这解决了我的问题,非常感谢。
    【解决方案4】:

    要实现这一点,您需要在您的类上使用 JsonTypeInfo 注释,尤其是 WRAPPER_OBJECT

    @JsonTypeName("foo")                                                                                         
    @JsonTypeInfo(include = JsonTypeInfo.As.WRAPPER_OBJECT ,use = JsonTypeInfo.Id.NAME)
    
    public class Bar(){
    )
    

    【讨论】:

      【解决方案5】:

      还有一个很好的注释:

      @JsonRootName(value = "my_pojo")
      public class MyPojo{
        ...
      }
      

      将生成:

      {
        "my_pojo" : {...}
      }
      

      【讨论】:

      • 但是这个没有JsonTypeInfo注解是行不通的。因为它也写在文档中:Annotation itself does not indicate that wrapping should be used
      【解决方案6】:

      最简单的解决方案怎么样?只需使用像这样的包装类:

      class Wrapper {
         public MyPojo MyPojo;
      }
      

      并在您的代码中包装/展开?

      除此之外,了解您为什么想要这样的附加 json 对象条目会有所帮助?我知道这是由通过 xml api 模拟 json 的库完成的(由于 xml 和 json 之间的阻抗,由于从 xml 到 json 的转换),但是对于纯 json 解决方案通常不需要。

      是否允许您弄清楚实际类型是什么? 如果是这样,也许您可​​以考虑启用多态类型信息,让杰克逊自动处理它? (有关详细信息,请参阅1.5 release notes,PTH 条目)。

      【讨论】:

      • 原因:因为我将序列化的 JSON 转换为 JSONObject,然后将该 JSONObject 附加到根 JSONObject。我的根对象是一个包含几种不同类型对象的传输对象。当我反序列化它们时,我需要知道另一端的类型。我有一个工作系统,我并没有那么恼火。感谢您的回复。
      • 嗯,好的。就像我说的那样,Jackson 可以在不包装的情况下自行确定类型,但其他库可能并非如此。很高兴知道无论哪种方式都可以正常工作。
      【解决方案7】:

      我使用了另一种方法,它对我有用。 我正在使用第三方 jar,所以我无法控制注释。 所以我不得不写一些hack。

      覆盖:org.codehaus.jackson.map.ser.BeanSerializerFactory.findBeanProperties(SerializationConfig, BasicBeanDescription)

      如下添加您的属性

      List<BeanPropertyWriter> props = super.findBeanProperties(config, beanDesc);
      BeanPropertyWriter bpw = null;
      try {
           Class cc = beanDesc.getType().getRawClass();
           Method m = cc.getMethod("getClass", null);
           bpw = new BeanPropertyWriter("$className", null, null, m, null,true, null);
      } catch (SecurityException e) {
        // TODO
      } catch (NoSuchMethodException e) {
        // TODO
      }
      props.add(bpw);
      return props;
      

      这样我可以得到更多的控制,也可以做其他类型的过滤器。

      【讨论】:

        【解决方案8】:

        我很想听听 OP 对此的解决方案。我遇到了类似的问题,我的 RESTful Web 服务正在将对象序列化为 XML 或 JSON 以供客户端使用。 Javascript 客户端需要知道包装类型以便对其进行解析。将类型与 URI 模式耦合不是一种选择。

        谢谢。

        编辑:我注意到 Spring MappingJacksonJsonMarshaller 在编组时添加了包装类,所以我在调试中单步执行代码并注意到 Spring 传入一个带有单个键值对的 HashMap,这样键是包装名称和价值是对象。因此,我扩展了 JacksonJaxbJsonProvider,重写了 writeTo() 方法并添加了以下内容:

        HashMap<String, Object> map = new HashMap<String, Object>();
        map.put(value.getClass().getSimpleName(), value);
        super.writeTo(map, type, genericType, annotations, mediaType, httpHeaders,entityStream);
        

        这有点小技巧,但效果很好。

        【讨论】:

        • 您能详细说明您的编辑吗?我不认为 MappingJacksonJsonMarshaller 存在,但这似乎是我遇到的问题的解决方案......或者至少值得尝试。
        • 对于 REST,您应该通过 conneg 执行此操作。使用“接受”标题。
        【解决方案9】:

        与根名称一起使用。

        objectMapper.writer().withRootName(MyPojo.class.getName());
        

        【讨论】:

          【解决方案10】:

          我通过经验发现,所有 JSON 都包含后端类型(作为字符串)和用于在前端呈现它的组件类型(如果使用 angular 或 Vue 之类的东西)是一个好主意。

          这样做的理由是您可以使用一组代码处理各种类型。

          例如,在 vue 中,在数据中包含 UI 组件的名称允许您在屏幕上仅使用父模板中的单个标记来呈现不同类型的子项列表。

            <component :is="child.componentType"/>.
          

          对于后端系统和 Web 服务 - 我更喜欢使用单个 Web 服务处理器类,它通过根据传入的有效负载查找适当的处理器类来为所有 Web 服务提供日志记录、审计和异常处理。这使得我的所有 Web 服务的实现看起来完全相同(大约 3 行代码),并且我在调用的生命周期中获得了详细的事件日志记录,而无需为每个服务编写任何代码。

          具有包装 JSON 的类型使其能够自我记录。如果您看到的只是属性,那么在找到相应的端点之前,您根本不知道自己在看什么。

          如果您想编写数据驱动的软件,能够识别您正在处理的内容是一项基本要求。

          【讨论】:

            【解决方案11】:
            @JsonTypeInfo(include=As.WRAPPER_OBJECT, use=Id.NAME) 
            

            正如 Arun Prakash 所建议的那样,此注释非常有效。我试图以这种形式获取 json:

            {"Rowset":{"ROW":{"receiptno":"881604199388936","status":"SUCCESS"}}}
            

            但是会变成这样:

            {"ROW":{"receiptno":"881604199388936","status":"SUCCESS"}}
            

            现在注释解决了我的问题。

            【讨论】:

              猜你喜欢
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 2012-03-17
              • 1970-01-01
              • 2010-11-22
              • 2015-04-29
              • 2012-02-02
              相关资源
              最近更新 更多