Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Conditional field requirement based on another field value in Jackson?

Consider a JSON representation with one string and two arrays. For example,

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

In the above case, type is required field, but ListA and ListB are conditionally required for deserialization based on the value of type. In other words, ListA is only required if type had value A and ListB is only required if type had a value B.

Currently, I am working in Jackson and in Java, and I have been able to implement making type field mandatory by creating POJO as following:

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

    // getter and setter auto-generated

But I can't just attach another @JsonProperty(required = true) to ListA or ListB since it's dependent on the value of type.

How can I conditionally require ListA and ListB for deserialization based on the value of type?

Also, I will be performing additional checks such as whether either ListA or ListB is an empty array (size == 0) or not.

like image 304
THIS USER NEEDS HELP Avatar asked Aug 09 '16 01:08

THIS USER NEEDS HELP


1 Answers

You could use a custom deserializer to achieve it.

Defining your model

Your Example class would be like:

public class Example {

    private String type;
    private List<Integer> listA;
    private List<Integer> listB;

    // Getters and setters omitted    
}

Creating a custom deserializer

Your custom deserializer could be as follwing:

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;
    }
}

Registering the custom deserializer

Register the custom deserializer defined above to your 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);

Testing your custom deserializer

Use the custom serializer:

String json = "{\"type\":\"A\",\"ListA\":[1,2,3]}";
Example example = mapper.readValue(json, Example.class);
like image 143
cassiomolin Avatar answered Nov 19 '22 22:11

cassiomolin