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

2024-04-15

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

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

我看到的都是一般注释

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

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

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


如果你想差异化null最通用的方法将使用来自缺失字段的值字段Map or JsonNode代替POJO. POJO类具有恒定的结构,Map or 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);
    }
}

现在看一些例子,我们测试什么JSON将生成为empty, null和缺失的节点。

Test 0-0

Input XML:

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

Result JSON is:

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

Test 0-1

Input XML:

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

Output JSON:

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

Test 0-2

Input XML:

<Root>
    <c/>
</Root>

Output JSON:

{"c":null}

这种简单快速的解决方案的最大问题是我们丢失了原语的类型信息。例如,如果b is 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算法如下:

  1. 将 JSON 读取为Map or JsonNode
  2. 查找所有字段名称
  3. Create FilterProvider找到的名称 - 请注意过滤器已注册allowedFields名称,与中使用的相同@JsonFilter注解。
  4. Convert Map to POJO用于类型强制。
  5. Write 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;
    }
}

Test 1-0

Input XML:

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

Output JSON:

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

Test 1-1

Input XML:

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

Output JSON:

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

Test 1-2

Input XML:

<Root>
    <c/>
</Root>

Output JSON:

{"c":null}

经过所有这些测试,我们看到动态检查字段是否为null, empty or absent这不是一件容易的事。即便如此,上述 2 个解决方案适用于简单模型,您应该测试它们以获取想要生成的所有响应。当模型很复杂并且包含许多复杂的注释时,例如:@JsonTypeInfo, @JsonSubTypes on Jackson侧面或@XmlElementWrapper, @XmlAnyElement on JAXB这使得这项任务很难执行。

我认为你的例子中最好的解决方案是使用@JsonInclude(NON_NULL)将所有设置字段发送给客户端XML side. null and absent应在客户端进行相同的处理。业务逻辑不应依赖于事实字段设置为null or absent in JSON有效负载。

也可以看看:

  • JSON 中空与 null 的约定是什么? https://stackoverflow.com/questions/9619852/what-is-the-convention-in-json-for-empty-vs-null
  • 在 JSON 中表示 null https://stackoverflow.com/questions/21120999/representing-null-in-json
  • 使用 Lombok 和 MapStruct 实现 PATCH 方法 https://stackoverflow.com/questions/74886852/how-to-deserialize-json-and-distinguish-null-from-the-absence-of-a-value
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

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

随机推荐