如何区分空值字段与杰克逊库中的缺失字段
Posted
技术标签:
【中文标题】如何区分空值字段与杰克逊库中的缺失字段【英文标题】:How to differentiate null value fields from absent fields in jackson library 【发布时间】:2019-08-16 04:17:34 【问题描述】:我们正在使用一个 API,而该 api 正在提供 xml 字段。我们必须为我们的消费者将 xml 转换为 json。我们需要将我们所拥有的内容显示为 XML 并仅显示那些字段。
-
如果字段存在且值存在
如果字段不存在,则不要显示它
如果存在空值/无值的字段,则按原样显示该字段。
我看到的是一般注释
@JsonInclude(NON_EMPTY)
可用于排除空值。我不能使用它,因为我仍然想在 json 中看到具有空值的空字段
@JsonInclude(NON_ABSENT)
可用于排除空值和“不存在”的值。我不能使用它,因为我仍然想查看 json 中的空字段和空字段。与JsonInclude (NON_NULL)
相同
所以我的问题是,如果我不指定任何这些属性,我可以实现我想要的吗?换句话说,如果我没有指定这些中的任何一个,杰克逊的行为是显示所有在动态意义上具有空值的字段?我主要关心的是这里的动态响应。对于每个请求,字段可能存在或不存在。我们必须在 json 中显示我们在 XML 中收到的确切内容
【问题讨论】:
【参考方案1】:如果您想区分null
值字段和缺失字段,最通用的方法是使用Map
或JsonNode
而不是POJO
。 POJO
类具有常量结构,Map
或 JsonNode
具有动态 - 仅包含您实际放置的内容。让我们创建一个简单的应用程序,它从文件中读取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);
现在看一些示例,我们测试将为empty
、null
和不存在的节点生成什么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
这种简单快速的解决方案最大的问题是我们丢失了原语的类型信息。例如,如果b
是Integer
,我们应该在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 -> Map -> JSON
算法更改为以下一个:
-
将 JSON 读取为
Map
或 JsonNode
查找所有字段名称
使用找到的名称创建FilterProvider
- 请注意,过滤器使用allowedFields
名称注册,与@JsonFilter
注释中使用的名称相同。
将 Map
转换为 POJO
以进行类型强制。
用过滤器写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
在所有这些测试之后,我们看到动态检查字段是否为null
、empty
或absent
并非易事。即便如此,以上 2 个解决方案适用于简单模型,您应该针对您想要生成的所有响应测试它们。当模型很复杂并且包含许多复杂的注释时,例如:Jackson
侧的@JsonTypeInfo
、@JsonSubTypes
或JAXB
侧的@XmlElementWrapper
、@XmlAnyElement
,这使得这项任务很难实现。
我认为您示例中的最佳解决方案是使用@JsonInclude(NON_NULL)
,它将XML
端的所有设置字段发送给客户端。 null
和 absent
在客户端应该被同等对待。业务逻辑不应依赖于 JSON
负载中的事实字段设置为 null
或 absent
。
另见:
what is the convention in JSON for empty vs. null? Representing null in JSON【讨论】:
非常感谢您提供的详细信息。我同意业务逻辑不应该依赖于显示空字段。它更多的是法律和合规性,而不是业务逻辑。 @Ags,我理解这些论点。我想,我展示了将XML
转换为JSON
的蓬松程度,这不是一件容易的事。我们应该深刻理解XML
中的empty
节点和JSON
中的empty
节点是什么意思。您可以创建一些规则来处理X
、Y
、Z
的情况,并尝试创建自定义解决方案。以上是关于如何区分空值字段与杰克逊库中的缺失字段的主要内容,如果未能解决你的问题,请参考以下文章
如何更改杰克逊以检测 POJO 中的所有字段,而不仅仅是公共字段?