Is there a way to deserialize the following xml into Map holding List of items using Jackson?
<order>
<number>12345678</number>
<amount>100.10</amount>
<items>
<item>
<itemId>123</itemId>
<amount>100.0</amount>
<itemName>Item Name1</itemName>
</item>
<item>
<itemId>234</itemId>
<amount>200.00</amount>
<itemName>Item Name1</itemName>
</item>
</items>
</order>
I tried with
XmlMapper mapper = new XmlMapper();
LinkedHashMap map = (LinkedHashMap)mapper.readValue(xml, Object.class);
and got the following Map. The first item in the list is missing.
{
order={
number=12345678,
amount=100.1,
items={
item={
amount=200.0,
itemName=ItemName2,
itemId=234
}
}
}
}
Configuration of XML Mapper Object XmlMapper extends ObjectMapper . Therefore, you can use XML in the same way that you use ObjectMapper . For example, register the Java 8 modules to enable the feature of parameter names, Java 8 time, and Java 8 data types.
Jackson is a library for handling JSON in Java systems and now has support for XML from version 2. DOM4J is a memory-efficient library for parsing XML, XPath, and XSLT (eXtensible Stylesheet Language).
Jackson is a powerful and efficient Java library that handles the serialization and deserialization of Java objects and their JSON representations. It's one of the most widely used libraries for this task, and runs under the hood of many other frameworks.
This is a known jackson-dataformat-xml
bug filed under issue 205. In a nutshell, duplicated elements in the XML get swallowed by the current UntypedObjectDeserializer
implementation. Fortunately, the author (João Paulo Varandas) of the report also provided a temporary fix in the form a custom UntypedObjectDeserializer
implementation. Below I share my interpretation of the fix:
import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.core.JsonToken;
import com.fasterxml.jackson.databind.DeserializationContext;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.deser.std.UntypedObjectDeserializer;
import com.fasterxml.jackson.databind.module.SimpleModule;
import com.fasterxml.jackson.dataformat.xml.XmlMapper;
import javax.annotation.Nullable;
import java.io.IOException;
import java.util.*;
public enum JacksonDataformatXmlIssue205Fix {;
public static void main(String[] args) throws IOException {
String xml = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n" +
"<items>\n" +
" <item><id>1</id></item>\n" +
" <item><id>2</id></item>\n" +
" <item><id>3</id></item>\n" +
"</items>";
SimpleModule module = new SimpleModule().addDeserializer(Object.class, Issue205FixedUntypedObjectDeserializer.getInstance());
XmlMapper xmlMapper = (XmlMapper) new XmlMapper().registerModule(module);
Object object = xmlMapper.readValue(xml, Object.class);
System.out.println(object); // {item=[{id=1}, {id=2}, {id=3}]}
}
@SuppressWarnings({ "deprecation", "serial" })
public static class Issue205FixedUntypedObjectDeserializer extends UntypedObjectDeserializer {
private static final Issue205FixedUntypedObjectDeserializer INSTANCE = new Issue205FixedUntypedObjectDeserializer();
private Issue205FixedUntypedObjectDeserializer() {}
public static Issue205FixedUntypedObjectDeserializer getInstance() {
return INSTANCE;
}
@Override
@SuppressWarnings({ "unchecked", "rawtypes" })
protected Object mapObject(JsonParser parser, DeserializationContext context) throws IOException {
// Read the first key.
@Nullable String firstKey;
JsonToken token = parser.getCurrentToken();
if (token == JsonToken.START_OBJECT) {
firstKey = parser.nextFieldName();
} else if (token == JsonToken.FIELD_NAME) {
firstKey = parser.getCurrentName();
} else {
if (token != JsonToken.END_OBJECT) {
throw context.mappingException(handledType(), parser.getCurrentToken());
}
return Collections.emptyMap();
}
// Populate entries.
Map<String, Object> valueByKey = new LinkedHashMap<>();
String nextKey = firstKey;
do {
// Read the next value.
parser.nextToken();
Object nextValue = deserialize(parser, context);
// Key conflict? Combine existing and current entries into a list.
if (valueByKey.containsKey(nextKey)) {
Object existingValue = valueByKey.get(nextKey);
if (existingValue instanceof List) {
List<Object> values = (List<Object>) existingValue;
values.add(nextValue);
} else {
List<Object> values = new ArrayList<>();
values.add(existingValue);
values.add(nextValue);
valueByKey.put(nextKey, values);
}
}
// New key? Put into the map.
else {
valueByKey.put(nextKey, nextValue);
}
} while ((nextKey = parser.nextFieldName()) != null);
// Ship back the collected entries.
return valueByKey;
}
}
}
The other answers don't work if you have to use readTree()
and JsonNode
. I know it's an ugly solution but at least you don't need to paste someone's gist in your project.
Add org.json to your project dependencies.
And then do the following:
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.json.JSONObject;
import org.json.XML;
...
private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper();
...
JSONObject soapDatainJsonObject = XML.toJSONObject(data);
return OBJECT_MAPPER.readTree(soapDatainJsonObject.toString());
The conversion goes as follows:
XML -> JSONObject (using org.json) -> string -> JsonNode (using readTree)
Of course, toJSONObject handles duplicates without any problems, I suggest to avoid using Jackson and readTree()
if you can.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With