【问题标题】:Custom Jackson serializer for a generic tree用于通用树的自定义 Jackson 序列化程序
【发布时间】:2013-01-28 04:55:05
【问题描述】:

假设我有一个用 Java 实现的参数化树,如下所示:

public class Tree<E> {
   private static class Node {
      E element;
      List<Node> children.
   }

   Node root;

   //... You get the idea.
}

这里的想法是,上面的实现只关心树的拓扑结构,而不知道将通过实例化存储在树中的元素。

现在,假设我希望我的树元素是地理。它们按树组织的原因是因为大陆包含国家,国家包含州或省,等等。为简单起见,地理具有名称和类型:

public class GeoElement { String name; String type; }

所以,地理层次结构最终看起来像这样:

public class Geography extends Tree<GeoElement> {}

现在到 Jackson 序列化。假设 Jackson 序列化器可以看到字段,则此实现的直接序列化将如下所示:

{
   "root": {
      "element": {
         "name":"Latin America",
         "type":"Continent"
      }
      "children": [
          {
             "element": {
                "name":"Brazil",
                "type":"Country"
             },
             "children": [
                 // ... A list of states in Brazil
             ]
          },
          {
             "element": {
                "name":"Argentina",
                "type":"Country"
             },
             "children": [
                 // ... A list of states in Argentina
             ]
          }
      ]
   }

这种 JSON 渲染并不好,因为它包含来自 Tree 和 Node 类的不必要的工件,即“根”和“元素”。我需要的是这个:

{
   "name":"Latin America",
   "type":"Continent"
   "children": [
       {
          "name":"Brazil",
          "type":"Country"
          "children": [
             // ... A list of states in Brazil
          ]
       },
       {
          "name":"Argentina",
          "type":"Country"
          "children": [
             // ... A list of states in Argentina
          ]
       }
   ]
}

非常感谢任何帮助。 -伊戈尔。

【问题讨论】:

  • 感谢您的建议,斯里尼瓦斯。它不起作用,因为我不想忽略节点,只想丢弃节点包装器。
  • 你找到解决办法了吗,如果有请更新
  • @anon StaxMan 提供的解决方案不行吗?
  • 我在类中没有根节点。节点 -> 数据,List 结构。在这里我必须创建一个单独的节点序列化程序并编写递归代码来完成它,

标签: java json generics jackson


【解决方案1】:

您需要的是@JsonUnwrapped

用于指示属性应该被序列化“解包”的注解;也就是说,如果将其序列化为 JSON 对象,则其属性将作为其包含对象的属性包含在内

将此注解添加到Node 类的Treeelement 字段的root 字段中,如下所示:

public class Tree<E> {
   private static class Node {

      @JsonUnwrapped
      E element;
      List<Node> children.
   }

   @JsonUnwrapped
   Node root;

   //... You get the idea.
}

它会给你你想要的输出:

{
    "name": "Latin America",
    "type": "Continent",
    "children": [{
        "name": "Brazil",
        "type": "Country",
        "children": []
    }, {
        "name": "Argentina",
        "type": "Country",
        "children": []
    }]
}

【讨论】:

    【解决方案2】:

    也许像这样使用@JsonValue

    public class Tree<E> {
      @JsonValue
      Node root;
    }
    

    如果你只需要“打开”你的树?

    【讨论】:

    • 我认为这行不通。它只会打开外部 root 对象。它不会解开 element 对象。
    【解决方案3】:

    您最好的选择是为您的对象构建和注册一个自定义序列化程序。

    定义你的序列化器:

    public class NodeSerializer extends StdSerializer<Node> {
    

    然后在你的 Node 类上:

    @JsonSerialize(using = NodeSerializer.class)
    public class Node {
    }
    

    NodeSerializer里面

    @Override
        public void serialize(
          Node node, JsonGenerator jgen, SerializerProvider provider) 
          throws IOException, JsonProcessingException {
    
            jgen.writeStartObject();
            jgen.writeStringField("name", node.element.name);
    
            jgen.writeStringField("type", node.element.type);
            //Process children
            serializeFields(node, jgen, provider);
            jgen.writeEndObject();
        }
    

    这个通用框架可以让您控制元素的序列化方式。您可能还需要 @JsonIgnore elementNode 内的对象,因为您的自定义序列化程序负责将该信息推送到生成的 JSON 中。网上有很多关于自定义序列化程序和覆盖默认 JSON 导出的内容。

    您可以通过类似的方式使用 Tree 实现的序列化程序摆脱 root。

    如果您不想在类上注册序列化程序,您也可以使用ObjectMapper 一次一个地注册:

    ObjectMapper mapper = new ObjectMapper();
    
    SimpleModule module = new SimpleModule();
    module.addSerializer(Node.class, new NodeSerializer());
    mapper.registerModule(module);
    
    String serialized = mapper.writeValueAsString(tree);
    

    注释方法将全局应用。这种方法允许对自定义序列化程序的使用方式/位置进行一些控制。

    【讨论】:

      【解决方案4】:

      要删除 element 类型,一种可能性是更改您的结构,以便名称和类型将直接包含在每个节点中:

      public class TreeGeo {
         private static class Node {
            String name;
            String type;
            List<Node> children.
         }
      
         Node root;
      }
      

      对于删除root 类型,我不知道。我想你可以从 jsonObject 中提取一个子对象,但我对 Jackson 了解不多。但是,您可以给它起一个更好的名称,例如 world,或者通过一些字符串操作手动操作生成的字符串以将其删除。

      【讨论】:

        猜你喜欢
        • 2017-09-06
        • 1970-01-01
        • 2018-11-17
        • 2014-08-02
        • 2018-03-06
        • 2020-04-11
        • 2011-11-01
        • 2019-08-10
        相关资源
        最近更新 更多