如何区分杰克逊库中的空值字段和缺失字段



我们正在使用一个API,而API正在提供xml字段。我们必须为我们的消费者将 xml 转换为 json。我们要求仅将我们得到的内容显示为XML并仅显示这些字段。

  1. 如果字段存在且存在值,则存在
  2. 如果字段不存在,则不要显示它
  3. 如果字段存在且值为 null/no,则按原样显示字段。

我所看到的是一般注释

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

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

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

如果要区分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 -> Map -> 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 种解决方案适用于简单模型,您应该针对要生成的所有响应测试它们。当模型很复杂并且包含许多复杂的注释时,例如:@JsonTypeInfo@JsonSubTypesJackson侧或@XmlElementWrapper@XmlAnyElementJAXB侧,这使得这项任务很难实现。

我认为您的示例中的最佳解决方案是使用@JsonInclude(NON_NULL)XML端的所有设置字段发送到客户端。nullabsent在客户端应以相同的方式对待。业务逻辑不应依赖于事实字段设置为nullabsentJSON有效负载。

另请参阅:

  • JSON 中空与空的约定是什么?
  • 在 JSON 中表示空
  • 使用 Lombok 和 MapStruct 实现 PATCH 方法

最新更新