【问题标题】:Conditional field requirement based on another field value in Jackson?基于杰克逊另一个字段值的条件字段要求?
【发布时间】:2016-12-14 21:42:49
【问题描述】:

考虑一个包含一个字符串和两个数组的 JSON 表示。例如,

{
    "type" : "A",
    "ListA" : []
    "ListB" : [3, 4, 5]
 }

在上述情况下,type 是必填字段,但根据 type 的值进行反序列化有条件地需要ListAListB。换句话说,ListA 仅在 type 具有值 A 时才需要,而 ListB 仅在 type 具有值 B 时才需要。

目前,我在 Jackson 和 Java 中工作,我已经能够通过创建 POJO 来实现使 type 字段成为强制性字段,如下所示:

public class Example {
    @JsonProperty(required = true)
    String type;

    // getter and setter auto-generated

但我不能将另一个@JsonProperty(required = true) 附加到ListAListB,因为它取决于type 的值。

如何根据type 的值有条件地要求ListAListB 进行反序列化?

此外,我还将执行其他检查,例如 ListAListB 是否为空数组 (size == 0)。

【问题讨论】:

    标签: java json jackson pojo


    【解决方案1】:

    使用 Jackson,您可以为您的示例 POJO 创建自己的自定义反序列化器,扩展 StdDeserializer 类并用您的逻辑覆盖 deserialize() 方法。在这里您可以检查类型和列表大小。

    然后,为了使用您的自定义反序列化器,您必须将其添加到 SimpleModule 并将后者注册到您的 Jackson ObjectMapper

    我之前写过几篇关于这个主题的文章,您可以在其中找到关于使用 Jackson 进行自定义序列化/反序列化的具体示例:

    Jackson: create and register a custom JSON serializer with StdSerializer and SimpleModule classes

    Jackson: create a custom JSON deserializer with StdDeserializer and JsonToken classes

    【讨论】:

    • 这如何回答这个问题?
    • 使用自定义反序列化器,他可以检查类型并填充预期的列表。如果没有可用的值,因此不满足条件“需要列表 X”,他可以生成异常并适时处理该对象。我认为这可以解决他正在寻找的东西。
    • 只是为了确保,您不能将 StdDeserializer 方法与 POJO 样式反序列化一起使用?
    • 其实我又看了一遍,我错了。所以基本上你是在使用反序列化器填充 POJO 模型中指定的字段?
    • 没错!使用您的自定义反序列化器,您可以选择如何使用收到的 json 以及要填充的 POJO 字段。在这种情况下,您可以根据您在 json 中收到的类型为您需要的字段定义“必需”策略。
    【解决方案2】:

    您可以使用自定义反序列化器来实现它。

    定义你的模型

    您的 Example 课程如下:

    public class Example {
    
        private String type;
        private List<Integer> listA;
        private List<Integer> listB;
    
        // Getters and setters omitted    
    }
    

    创建自定义反序列化程序

    您的自定义反序列化器可能如下所示:

    public class ExampleDeserializer extends StdDeserializer<Example> {
    
        private static final String TYPE_A = "A";
        private static final String TYPE_B = "B";
    
        public ExampleDeserializer() {
            super(Example.class);
        }
    
        @Override
        public Example deserialize(JsonParser p, DeserializationContext ctxt) 
                       throws IOException, JsonProcessingException {
    
            ObjectMapper mapper = (ObjectMapper) p.getCodec();  
            JsonNode tree = mapper.readTree(p);  
    
            Example example = new Example();
    
            JsonNode typeNode = tree.get("type");
            if (typeNode == null || typeNode.asText().isEmpty()) {
                throw ctxt.mappingException("\"type\" is required");
            }
            example.setType(typeNode.asText());
    
            switch (typeNode.asText()) {
    
            case TYPE_A:
                ArrayNode listANode = (ArrayNode) tree.get("ListA");
                if (listANode == null || listANode.size() == 0) {
                    throw ctxt.mappingException(
                               "\"ListA\" is required when \"type\" is \"" + TYPE_A + "\"");
                }
                example.setListA(createList(listANode));
                break;
    
            case TYPE_B:
                ArrayNode listBNode = (ArrayNode) tree.get("ListB");
                if (listBNode == null || listBNode.size() == 0) {
                    throw ctxt.mappingException(
                               "\"ListB\" is required when \"type\" is \"" + TYPE_B + "\"");
                }
                example.setListB(createList(listBNode));
                break;
    
            default:
                throw ctxt.mappingException(
                           "\"type\" must be \"" + TYPE_A + "\" or \"" + TYPE_B + "\"");
            }
    
    
            return example;
        }
    
        private List<Integer> createList(ArrayNode arrayNode) {
            List<Integer> list = new ArrayList<Integer>();
            for (JsonNode node : arrayNode) {
                list.add(node.asInt());
            }
            return list;
        }
    }
    

    注册自定义反序列化器

    将上面定义的自定义反序列化器注册到您的ObjectMapper

    SimpleModule module = new SimpleModule("ExampleDeserializer", 
            new Version(1, 0, 0, null, "com.example", "example-deserializer")); 
    
    ExampleDeserializer exampleDeserializer = new ExampleDeserializer();
    module.addDeserializer(Example.class, exampleDeserializer);
    
    ObjectMapper mapper = new ObjectMapper()
                              .registerModule(module)
                              .enable(SerializationFeature.INDENT_OUTPUT);
    

    测试您的自定义反序列化程序

    使用自定义序列化程序:

    String json = "{\"type\":\"A\",\"ListA\":[1,2,3]}";
    Example example = mapper.readValue(json, Example.class);
    

    【讨论】:

    • OP 的问题是关于反序列化的。我不相信序列化时有任何“必填”字段的概念。
    • @CássioMazzochiMolin 对不起。我应该澄清一下。我无法控制序列化过程。我只接收特定格式的 JSON,需要反序列化它。我会更新我的问题
    • 你使用ObjectNode而不是JsonNode有什么原因吗?
    • @THISUSERNEEDSHELP 使用ObjectNode 代替JsonNode 并没有什么特别的原因。我刚刚更新了代码。感谢您指出这一点。
    • @CássioMazzochiMolin 是否可以混合 POJO 插入和自定义反序列化?例如,除了typeListAListB 属性之外,我还有name 属性,我想直接通过POJO 添加而不需要自定义反序列化。这可能吗?
    最近更新 更多