【问题标题】:How to differentiate null value fields from absent fields in jackson library如何区分空值字段与杰克逊库中的缺失字段
【发布时间】:2019-08-16 04:17:34
【问题描述】:

我们正在使用一个 API,而该 api 正在提供 xml 字段。我们必须为我们的消费者将 xml 转换为 json。我们需要将我们所拥有的内容显示为 XML 并仅显示那些字段。

  1. 如果字段存在且值存在
  2. 如果字段不存在,则不要显示它
  3. 如果存在空值/无值的字段,则按原样显示该字段。

我看到的是一般注释

@JsonInclude(NON_EMPTY) 可用于排除空值。我不能使用它,因为我仍然想在 json 中看到具有空值的空字段

@JsonInclude(NON_ABSENT) 可用于排除空值和“不存在”的值。我不能使用它,因为我仍然想查看 json 中的空字段和空字段。与JsonInclude (NON_NULL) 相同

所以我的问题是,如果我不指定任何这些属性,我可以实现我想要的吗?换句话说,如果我没有指定这些中的任何一个,杰克逊的行为是显示所有在动态意义上具有空值的字段?我主要关心的是这里的动态响应。对于每个请求,字段可能存在或不存在。我们必须在 json 中显示我们在 XML 中收到的确切内容

【问题讨论】:

    标签: java json xml jackson


    【解决方案1】:

    如果您想区分null 值字段和缺失字段,最通用的方法是使用MapJsonNode 而不是POJOPOJO 类具有常量结构,MapJsonNode 具有动态 - 仅包含您实际放置的内容。让我们创建一个简单的应用程序,它从文件中读取XML 有效负载并创建JSON 响应:

    import com.fasterxml.jackson.databind.ObjectMapper;
    import com.fasterxml.jackson.dataformat.xml.XmlMapper;
    
    import java.io.File;
    import java.util.Map;
    
    public class JsonApp {
    
        public static void main(String[] args) throws Exception {
            File xmlFile = new File("./resource/test.xml").getAbsoluteFile();
    
            XmlMapper xmlMapper = new XmlMapper();
            Map map = xmlMapper.readValue(xmlFile, Map.class);
    
            ObjectMapper jsonMapper = new ObjectMapper();
            String json = jsonMapper.writeValueAsString(map);
    
            System.out.println(json);
        }
    }
    

    现在看一些示例,我们测试将为emptynull 和不存在的节点生成什么JSON

    测试 0-0

    输入XML:

    <Root>
        <a>A</a>
        <b>1</b>
        <c>
            <c1>Rick</c1>
            <c2>58</c2>
        </c>
    </Root>
    

    结果JSON 是:

    {"a":"A","b":"1","c":{"c1":"Rick","c2":"58"}}
    

    测试 0-1

    输入XML:

    <Root>
        <a>A</a>
        <c>
            <c1>Rick</c1>
            <c2/>
        </c>
    </Root>
    

    输出JSON:

    {"a":"A","c":{"c1":"Rick","c2":null}}
    

    测试 0-2

    输入XML:

    <Root>
        <c/>
    </Root>
    

    输出JSON:

    {"c":null}
    

    这种简单快速的解决方案最大的问题是我们丢失了原语的类型信息。例如,如果bInteger,我们应该在JSON 中将它作为没有引号的数字原语返回:" 周围的字符。为了解决这个问题,我们应该使用POJO 模型,它允许我们找到所有需要的类型。让我们为示例创建POJO 模型:

    @JsonFilter("allowedFields")
    class Root {
        private String a;
        private Integer b;
        private C c;
    
        // getters, setters
    }
    
    @JsonFilter("allowedFields")
    class C {
        private String c1;
        private Integer c2;
    
        // getters, setters
    }
    

    我们需要将简单的XML -&gt; Map -&gt; JSON 算法更改为以下一个:

    1. 将 JSON 读取为 MapJsonNode
    2. 查找所有字段名称
    3. 使用找到的名称创建FilterProvider - 请注意,过滤器使用allowedFields 名称注册,与@JsonFilter 注释中使用的名称相同。
    4. Map 转换为 POJO 以进行类型强制。
    5. 用过滤器写POJO

    简单的应用程序可能如下所示:

    import com.fasterxml.jackson.annotation.JsonFilter;
    import com.fasterxml.jackson.databind.JsonNode;
    import com.fasterxml.jackson.databind.ObjectMapper;
    import com.fasterxml.jackson.databind.node.ArrayNode;
    import com.fasterxml.jackson.databind.node.ObjectNode;
    import com.fasterxml.jackson.databind.ser.impl.SimpleBeanPropertyFilter;
    import com.fasterxml.jackson.databind.ser.impl.SimpleFilterProvider;
    import com.fasterxml.jackson.dataformat.xml.XmlMapper;
    
    import java.io.File;
    import java.util.HashSet;
    import java.util.LinkedList;
    import java.util.Set;
    
    public class JsonApp {
    
        public static void main(String[] args) throws Exception {
            File xmlFile = new File("./resource/test.xml").getAbsoluteFile();
    
            NodesWalker walker = new NodesWalker();
    
            XmlMapper xmlMapper = new XmlMapper();
            JsonNode root = xmlMapper.readValue(xmlFile, JsonNode.class);
            Set<String> names = walker.findAllNames(root);
    
            SimpleFilterProvider filterProvider = new SimpleFilterProvider();
            filterProvider.addFilter("allowedFields", SimpleBeanPropertyFilter.filterOutAllExcept(names));
    
            ObjectMapper jsonMapper = new ObjectMapper();
            jsonMapper.setFilterProvider(filterProvider);
    
            Root rootConverted = jsonMapper.convertValue(root, Root.class);
            String json = jsonMapper.writeValueAsString(rootConverted);
    
            System.out.println(json);
        }
    }
    
    class NodesWalker {
    
        public Set<String> findAllNames(JsonNode node) {
            Set<String> names = new HashSet<>();
    
            LinkedList<JsonNode> nodes = new LinkedList<>();
            nodes.add(node);
            while (nodes.size() > 0) {
                JsonNode first = nodes.removeFirst();
                if (first.isObject()) {
                    ObjectNode objectNode = (ObjectNode) first;
                    objectNode.fields().forEachRemaining(e -> {
                        names.add(e.getKey());
                        JsonNode value = e.getValue();
                        if (value.isObject() || value.isArray()) {
                            nodes.add(value);
                        }
                    });
                } else if (first.isArray()) {
                    ArrayNode arrayNode = (ArrayNode) first;
                    arrayNode.elements().forEachRemaining(e -> {
                        if (e.isObject() || e.isArray()) {
                            nodes.add(e);
                        }
                    });
                }
            }
    
            return names;
        }
    }
    

    测试 1-0

    输入XML:

    <Root>
        <a>A</a>
        <b>1</b>
        <c>
            <c1>Rick</c1>
            <c2>58</c2>
        </c>
    </Root>
    

    输出JSON:

    {"a":"A","b":1,"c":{"c1":"Rick","c2":58}}
    

    测试 1-1

    输入XML:

    <Root>
        <b>1</b>
        <c>
            <c2/>
        </c>
    </Root>
    

    输出JSON:

    {"b":1,"c":{"c2":null}}
    

    测试 1-2

    输入XML:

    <Root>
        <c/>
    </Root>
    

    输出JSON:

    {"c":null}
    

    在所有这些测试之后,我们看到动态检查字段是否为nullemptyabsent 并非易事。即便如此,以上 2 个解决方案适用于简单模型,您应该针对您想要生成的所有响应测试它们。当模型很复杂并且包含许多复杂的注释时,例如:Jackson 侧的@JsonTypeInfo@JsonSubTypesJAXB 侧的@XmlElementWrapper@XmlAnyElement,这使得这项任务很难实现。

    我认为您示例中的最佳解决方案是使用@JsonInclude(NON_NULL),它将XML 端的所有设置字段发送给客户端。 nullabsent 在客户端应该被同等对待。业务逻辑不应依赖于 JSON 负载中的事实字段设置为 nullabsent

    另见:

    【讨论】:

    • 非常感谢您提供的详细信息。我同意业务逻辑不应该依赖于显示空字段。它更多的是法律和合规性,而不是业务逻辑。
    • @Ags,我理解这些论点。我想,我展示了将XML 转换为JSON 的蓬松程度,这不是一件容易的事。我们应该深刻理解XML中的empty节点和JSON中的empty节点是什么意思。您可以创建一些规则来处理XYZ 的情况,并尝试创建自定义解决方案。
    猜你喜欢
    • 2013-08-16
    • 1970-01-01
    • 1970-01-01
    • 2015-03-20
    • 2015-06-21
    • 1970-01-01
    • 2021-04-02
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多