[英]How to differentiate null value fields from absent fields in jackson library
We are consuming an API and the api is providing xml fields.我们正在使用 API,该 API 提供 xml 字段。 We have to convert xml to json for our consumers.我们必须为我们的消费者将 xml 转换为 json。 We have a requirement of showing just what we have got as XML and to display only those fields .我们需要以 XML 形式显示我们所获得的内容,并且只显示那些字段。
What I have seen is general annotations我看到的是一般注释
@JsonInclude(NON_EMPTY)
can be used to exclude values that are empty.I cannot use this because I still want to see the empty fields with null value in json @JsonInclude(NON_EMPTY)
可用于排除空值。我不能使用它,因为我仍然想在 json 中看到具有空值的空字段
@JsonInclude(NON_ABSENT)
can be used to exclude null values and values that are "absent".I cannot use this because I still want to see the empty fields and null fields in json. @JsonInclude(NON_ABSENT)
可用于排除空值和“不存在”的值。我不能使用它,因为我仍然想在 json 中看到空字段和空字段。 Same with the JsonInclude (NON_NULL)
与JsonInclude (NON_NULL)
So my question is if I don't specify any of these properties can I achieve what I want ?所以我的问题是,如果我不指定任何这些属性,我可以实现我想要的吗? In other words if I don't specify any of these is jackson's behavior is to show all the fields that are have the null value on dynamic sense ?换句话说,如果我不指定其中任何一个,jackson 的行为是显示动态意义上具有空值的所有字段? My major concern is the dynamic response here .我主要关心的是这里的动态响应。 For each requests the fields may be present or not be present .对于每个请求,字段可能存在或不存在。 We have to show in the json what exactly we receive in XML我们必须在 json 中显示我们在 XML 中接收到的内容
If you want to differentiate null
value fields from absent fields the most generic method will be using Map
or JsonNode
instead of POJO
.如果您想区分null
值字段和不存在的字段,最通用的方法将使用Map
或JsonNode
而不是POJO
。 POJO
class has constant structure, Map
or JsonNode
have dynamic - contains only what you actually put there. POJO
类具有恒定结构, Map
或JsonNode
具有动态-仅包含您实际放置在那里的内容。 Let's create a simple app which reads XML
payload from file and creates JSON
response:让我们创建一个简单的应用程序,它从文件中读取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);
}
}
Now take a look on some examples where we test what JSON
will be generated for empty
, null
and absent nodes.现在看一些示例,其中我们测试将为empty
节点、 null
节点和不存在节点生成什么JSON
。
Input XML
:输入XML
:
<Root>
<a>A</a>
<b>1</b>
<c>
<c1>Rick</c1>
<c2>58</c2>
</c>
</Root>
Result JSON
is:结果JSON
是:
{"a":"A","b":"1","c":{"c1":"Rick","c2":"58"}}
Input XML
:输入XML
:
<Root>
<a>A</a>
<c>
<c1>Rick</c1>
<c2/>
</c>
</Root>
Output JSON
:输出JSON
:
{"a":"A","c":{"c1":"Rick","c2":null}}
Input XML
:输入XML
:
<Root>
<c/>
</Root>
Output JSON
:输出JSON
:
{"c":null}
The biggest problem with this simple and fast solution is we lost type information for primitives.这种简单快速的解决方案的最大问题是我们丢失了原语的类型信息。 For example, if b
is Integer
we should return it in JSON
as number primitive which does not have quotes: "
chars around. To solve this problem we should use POJO
model which allows us to find all required types. Let's create POJO
model for our example:例如,如果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
}
We need to change our simple XML -> Map -> JSON
algorithm to below one:我们需要将简单的XML -> Map -> JSON
算法更改为以下算法:
Map
or JsonNode
将 JSON 读取为Map
或JsonNode
FilterProvider
with found names - notice that filter is registered with allowedFields
name, the same which is used in @JsonFilter
annotation.使用找到的名称创建FilterProvider
- 请注意,过滤器使用allowedFields
名称注册,与@JsonFilter
注释中使用的名称相同。Map
to POJO
for type coercion.将Map
转换为POJO
以进行类型强制。POJO
with filter用过滤器编写POJO
Simple app could look like this:简单的应用程序可能如下所示:
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;
}
}
Input XML
:输入XML
:
<Root>
<a>A</a>
<b>1</b>
<c>
<c1>Rick</c1>
<c2>58</c2>
</c>
</Root>
Output JSON
:输出JSON
:
{"a":"A","b":1,"c":{"c1":"Rick","c2":58}}
Input XML
:输入XML
:
<Root>
<b>1</b>
<c>
<c2/>
</c>
</Root>
Output JSON
:输出JSON
:
{"b":1,"c":{"c2":null}}
Input XML
:输入XML
:
<Root>
<c/>
</Root>
Output JSON
:输出JSON
:
{"c":null}
After all these tests we see that dynamic checking whether field is null
, empty
or absent
is not an easy task.在所有这些测试之后,我们看到动态检查 field 是null
、 empty
还是absent
并不是一件容易的事。 Even so, above 2 solutions work for simple models you should test them for all responses you want to generate.即便如此,以上 2 个解决方案适用于简单模型,您应该针对您想要生成的所有响应测试它们。 When model is complex and contains many complex annotations such as: @JsonTypeInfo
, @JsonSubTypes
on Jackson
side or @XmlElementWrapper
, @XmlAnyElement
on JAXB
side it make this task very hard to implement.当模型很复杂并且包含许多复杂的注释时,例如: @JsonTypeInfo
、 @JsonSubTypes
在Jackson
端或@XmlElementWrapper
、 @XmlAnyElement
在JAXB
端,它会使这个任务很难实现。
I think the best solution in your example is to use @JsonInclude(NON_NULL)
which send to client all set fields on XML
side.我认为您示例中的最佳解决方案是使用@JsonInclude(NON_NULL)
将XML
端的所有设置字段发送给客户端。 null
and absent
should be treated on client side identically. null
和absent
应该在客户端被同等对待。 Business logic should not rely on the fact field is set to null
or absent
in JSON
payload.业务逻辑不应依赖于事实字段在JSON
负载中设置为null
或absent
。
See also:也可以看看:
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.