I have some xml that looks as follows:
<model>
<action>
<items>
<input name="i1"/>
<input name="i2"/>
<output name="o1"/>
<output name="o2"/>
</items>
</action>
</model>
I'm using the Jackson XMLMapper to deserialize it:
Model m = xmlMapper.readValue(file, Model.class);
My Model class contains an Action property (with the @JsonProperty("action")
annotation)
My Action class that contains a List<Input>
and a List<Output>
Both the lists are empty after deserialization.
I can't figure out what annotations I should use to indicate that the items tag contains both inputs and outputs.
I have tried the @JacksonXmlElementWrapper
annotation on both lists, but that doesn't seem to work.
Jackson does automatically deserialize xml elements with same name into an array but it will fail if multiple arrays are in the same level. You are going to have to use custom deserializer. here is an example custom deserializer that can successfully deserialize example XML doc
public class ActionDeserializer extends StdDeserializer<Action> {
public ActionDeserializer() {
super((Class<?>)null);
}
public ActionDeserializer(Class<?> vc) {
super(vc);
}
@Override
public Action deserialize(JsonParser parser, DeserializationContext ctx) throws IOException {
Action action = new Action();
action.inputs = new ArrayList<>();
action.outputs = new ArrayList<>();
// <action>
JsonNode actionNode = parser.getCodec().readTree(parser);
// <items>
JsonNode itemsNode = actionNode.get("items");
// deserialize <input> elements into pojo list
JsonNode inputNode = itemsNode.get("input");
if (inputNode.getNodeType() == JsonNodeType.ARRAY) {
Iterator<JsonNode> iterator = inputNode.iterator();
while (iterator.hasNext()) {
action.inputs.add(new XmlMapper().treeToValue(iterator.next(), Input.class));
}
}
// deserialize <output> elements into pojo list
JsonNode outputNode = itemsNode.get("output");
if (outputNode.getNodeType() == JsonNodeType.ARRAY) {
Iterator<JsonNode> iterator = outputNode.iterator();
while (iterator.hasNext()) {
action.outputs.add(new XmlMapper().treeToValue(iterator.next(), Output.class));
}
}
return action;
}
}
you register the custom deserializer using simple module
SimpleModule simpleModule = new SimpleModule();
simpleModule.addDeserializer(Action.class, new ActionDeserializer());
xmlMapper.registerModule(simpleModule);
EDIT:
here is my complete test class. the custom deserializer works even without the condition on type of json node
public class Model {
public Action action;
public static void main(String[] args) throws JsonProcessingException {
String xml = """
<model>
<action>
<items>
<input name="i1"/>
<input name="i2"/>
<output name="o1"/>
<output name="o2"/>
</items>
</action>
</model>
""";
XmlMapper xmlMapper = new XmlMapper();
SimpleModule simpleModule = new SimpleModule();
simpleModule.addDeserializer(Action.class, new ActionDeserializer());
xmlMapper.registerModule(simpleModule);
Model m = xmlMapper.readValue(xml, Model.class);
System.out.println("inputs " + m.action.inputs.stream().map(x -> x.name).toList());
System.out.println("outputs " + m.action.outputs.stream().map(x -> x.name).toList());
}
public static class Action {
public List<Input> inputs;
public List<Output> outputs;
}
public static class Input {
public String name;
}
public static class Output {
public String name;
}
public static class ActionDeserializer extends StdDeserializer<Action> {
public ActionDeserializer() {
super((Class<?>)null);
}
@Override
public Action deserialize(JsonParser parser, DeserializationContext ctx) throws IOException {
Action action = new Action();
action.inputs = new ArrayList<>();
action.outputs = new ArrayList<>();
// <action>
JsonNode actionNode = parser.getCodec().readTree(parser);
// <items>
JsonNode itemsNode = actionNode.get("items");
// deserialize <input> elements into pojo list
JsonNode inputNode = itemsNode.get("input");
Iterator<JsonNode> iterator = inputNode.iterator();
while (iterator.hasNext()) {
action.inputs.add(new XmlMapper().treeToValue(iterator.next(), Input.class));
}
// deserialize <output> elements into pojo list
JsonNode outputNode = itemsNode.get("output");
iterator = outputNode.iterator();
while (iterator.hasNext()) {
action.outputs.add(new XmlMapper().treeToValue(iterator.next(), Output.class));
}
return action;
}
}
}
output
inputs [i1, i2]
outputs [o1, o2]