10

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.

THIS USER NEEDS HELP
  • 3,136
  • 4
  • 30
  • 55

2 Answers2

9

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);
cassiomolin
  • 124,154
  • 35
  • 280
  • 359
  • OP's question is about deserialization. I don't believe there's any concept of "required" fields when serializing. – shmosel Aug 09 '16 at 16:01
  • @CássioMazzochiMolin Sorry. I should have clarified. I do not have control over serialization process. I only receive JSON in particular format and need to deserialize it. I will update my question – THIS USER NEEDS HELP Aug 09 '16 at 16:31
  • Is there a reason why you used `ObjectNode` instead of `JsonNode`? – THIS USER NEEDS HELP Aug 10 '16 at 17:47
  • @THISUSERNEEDSHELP There's no particular reason for using `ObjectNode` instead of `JsonNode`. I've just updated the code. Thanks for pointing that. – cassiomolin Aug 10 '16 at 21:14
  • @CássioMazzochiMolin Is it possible to mix POJO insertion and custom deserialization? For example, say other than `type`, `ListA`, and `ListB` properties, I have `name` property that I would like to add directly through POJO without custom deserialization. Would that be possible? – THIS USER NEEDS HELP Aug 15 '16 at 22:11
  • Hello, I created a new question due to a new [need](http://stackoverflow.com/questions/39603910/jackson-custom-filter-with-full-pojo-data-bind) and I was wondering if you could take a look at it? – THIS USER NEEDS HELP Sep 20 '16 at 21:21
0

With Jackson you can create your own custom Deserializer for your Example POJO, extending StdDeserializer class and overriding the deserialize() method with your logic. Here you can check the type and the lists size.

Then, in order to use your custom Deserializer you have to add it to a SimpleModule and register tha latter with your Jackson ObjectMapper

I wrote a couple of articles times ago on this topic where you can find a concrete example about custom Serialization/Deserialization with 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

Davis Molinari
  • 741
  • 1
  • 5
  • 20
  • How does this answer the question? – shmosel Aug 09 '16 at 09:46
  • Using custom deserializer he can check the type and populate the expected list. If no values are available, so the condition "List X is required" is not satisfied, he can generate an exception and opportunely handle the object. I think this can solve what he is looking for. – Davis Molinari Aug 09 '16 at 15:56
  • Just to make sure, you can't use StdDeserializer method with POJO style deserialization? – THIS USER NEEDS HELP Aug 09 '16 at 17:06
  • Actually, I read it again, and I am mistaken. So basically you are populating the fields specified in POJO model using deserializer? – THIS USER NEEDS HELP Aug 09 '16 at 17:18
  • 2
    Exactly! With your custom deserializer you can choose how to use the json you receive and which POJO fields to populate. In this case you can define your "is required" strategy for the fields you need, based on the type you receive in the json. – Davis Molinari Aug 10 '16 at 07:13
  • Hello, I had to create a new question due to a new [need](http://stackoverflow.com/questions/39603910/jackson-custom-filter-with-full-pojo-data-bind), and I was wondering if you could take a look? – THIS USER NEEDS HELP Sep 20 '16 at 21:21