Reputation: 3266
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.
Upvotes: 10
Views: 9994
Reputation: 130867
You could use a custom deserializer to achieve it.
Your Example
class would be like:
public class Example {
private String type;
private List<Integer> listA;
private List<Integer> listB;
// Getters and setters omitted
}
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;
}
}
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);
Use the custom serializer:
String json = "{\"type\":\"A\",\"ListA\":[1,2,3]}";
Example example = mapper.readValue(json, Example.class);
Upvotes: 9
Reputation: 761
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
Upvotes: 0