Как преобразовать XML в java.util.Map и наоборот
Я ищу легкий API (предпочтительный одиночный класс) для преобразования
Map<String,String> map = new HashMap<String,String();
в xml и, наоборот, преобразует XML обратно в карту.
Пример:
Map<String,String> map = new HashMap<String,String();
map.put("name","chris");
map.put("island","faranga");
MagicAPI.toXML(map,"root");
результат:
<root>
<name>chris</chris>
<island>faranga</island>
</root>
и обратно:
Map<String,String> map = MagicAPI.fromXML("...");
Я не хочу использовать JAXB или API конверсии JSON. Он не должен заботиться о вложенных картах или атрибутах или что-то еще, просто в этом простом случае.
Любые предложения?
Изменить. Я создал рабочую копию и вставьте образец. Благодаря fvu и Михал Бернхард.
Загрузите последнюю инфраструктуру XStream, достаточно "ядра".
Map<String,Object> map = new HashMap<String,Object>();
map.put("name","chris");
map.put("island","faranga");
// convert to XML
XStream xStream = new XStream(new DomDriver());
xStream.alias("map", java.util.Map.class);
String xml = xStream.toXML(map);
// from XML, convert back to map
Map<String,Object> map2 = (Map<String,Object>) xStream.fromXML(xml);
Никаких преобразователей или чего-либо еще не требуется. Просто xstream-x.y.z.jar достаточно.
Ответы
Ответ 1
XStream!
Обновлено: я добавил немаршальную часть в соответствии с запросом в комментариях.
import com.thoughtworks.xstream.XStream;
import com.thoughtworks.xstream.converters.Converter;
import com.thoughtworks.xstream.converters.MarshallingContext;
import com.thoughtworks.xstream.converters.UnmarshallingContext;
import com.thoughtworks.xstream.io.HierarchicalStreamReader;
import com.thoughtworks.xstream.io.HierarchicalStreamWriter;
import java.util.AbstractMap;
import java.util.HashMap;
import java.util.Map;
public class Test {
public static void main(String[] args) {
Map<String,String> map = new HashMap<String,String>();
map.put("name","chris");
map.put("island","faranga");
XStream magicApi = new XStream();
magicApi.registerConverter(new MapEntryConverter());
magicApi.alias("root", Map.class);
String xml = magicApi.toXML(map);
System.out.println("Result of tweaked XStream toXml()");
System.out.println(xml);
Map<String, String> extractedMap = (Map<String, String>) magicApi.fromXML(xml);
assert extractedMap.get("name").equals("chris");
assert extractedMap.get("island").equals("faranga");
}
public static class MapEntryConverter implements Converter {
public boolean canConvert(Class clazz) {
return AbstractMap.class.isAssignableFrom(clazz);
}
public void marshal(Object value, HierarchicalStreamWriter writer, MarshallingContext context) {
AbstractMap map = (AbstractMap) value;
for (Object obj : map.entrySet()) {
Map.Entry entry = (Map.Entry) obj;
writer.startNode(entry.getKey().toString());
Object val = entry.getValue();
if ( null != val ) {
writer.setValue(val.toString());
}
writer.endNode();
}
}
public Object unmarshal(HierarchicalStreamReader reader, UnmarshallingContext context) {
Map<String, String> map = new HashMap<String, String>();
while(reader.hasMoreChildren()) {
reader.moveDown();
String key = reader.getNodeName(); // nodeName aka element name
String value = reader.getValue();
map.put(key, value);
reader.moveUp();
}
return map;
}
}
}
Ответ 2
Здесь конвертер для XStream, включая unmarshall
public class MapEntryConverter implements Converter{
public boolean canConvert(Class clazz) {
return AbstractMap.class.isAssignableFrom(clazz);
}
public void marshal(Object value, HierarchicalStreamWriter writer, MarshallingContext context) {
AbstractMap<String,String> map = (AbstractMap<String,String>) value;
for (Entry<String,String> entry : map.entrySet()) {
writer.startNode(entry.getKey().toString());
writer.setValue(entry.getValue().toString());
writer.endNode();
}
}
public Object unmarshal(HierarchicalStreamReader reader, UnmarshallingContext context) {
Map<String, String> map = new HashMap<String, String>();
while(reader.hasMoreChildren()) {
reader.moveDown();
map.put(reader.getNodeName(), reader.getValue());
reader.moveUp();
}
return map;
}
Ответ 3
Как насчет XStream? Не 1 класс, а 2 баночки для многих вариантов использования, включая ваш, очень простой в использовании, но достаточно мощный.
Ответ 4
Один из вариантов - это сделать свой собственный. Это было бы довольно просто:
Document doc = getDocument();
Element root = doc.createElement(rootName);
doc.appendChild(root);
for (Map.Entry<String,String> element : map.entrySet() ) {
Element e = doc.createElement(element.getKey());
e.setTextContent(element.getValue());
root.appendChild(e);
}
save(doc, file);
а нагрузка - равномерно getChildNodes
и цикл. Уверен, что у него есть немного плиты котла, которые требуют боги XML, но это не более 1 часа работы.
Или вы можете посмотреть Properties, если вы не слишком слиты в формате XML.
Ответ 5
Я использовал подход с пользовательским конвертером:
public static class MapEntryConverter implements Converter {
public boolean canConvert(Class clazz) {
return AbstractMap.class.isAssignableFrom(clazz);
}
public void marshal(Object value, HierarchicalStreamWriter writer, MarshallingContext context) {
AbstractMap map = (AbstractMap) value;
for (Object obj : map.entrySet()) {
Entry entry = (Entry) obj;
writer.startNode(entry.getKey().toString());
context.convertAnother(entry.getValue());
writer.endNode();
}
}
public Object unmarshal(HierarchicalStreamReader reader, UnmarshallingContext context) {
// dunno, read manual and do it yourself ;)
}
}
Но я изменил сериализацию значения карт для делегирования в MarshallingContext. Это должно улучшить решение для работы с составными значениями карты и вложенными картами.
Ответ 6
Я написал фрагмент кода, который преобразует XML-контент в многослойную структуру карт:
public static Object convertNodesFromXml(String xml) throws Exception {
InputStream is = new ByteArrayInputStream(xml.getBytes());
DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
dbf.setNamespaceAware(true);
DocumentBuilder db = dbf.newDocumentBuilder();
Document document = db.parse(is);
return createMap(document.getDocumentElement());
}
public static Object createMap(Node node) {
Map<String, Object> map = new HashMap<String, Object>();
NodeList nodeList = node.getChildNodes();
for (int i = 0; i < nodeList.getLength(); i++) {
Node currentNode = nodeList.item(i);
String name = currentNode.getNodeName();
Object value = null;
if (currentNode.getNodeType() == Node.ELEMENT_NODE) {
value = createMap(currentNode);
}
else if (currentNode.getNodeType() == Node.TEXT_NODE) {
return currentNode.getTextContent();
}
if (map.containsKey(name)) {
Object os = map.get(name);
if (os instanceof List) {
((List<Object>)os).add(value);
}
else {
List<Object> objs = new LinkedList<Object>();
objs.add(os);
objs.add(value);
map.put(name, objs);
}
}
else {
map.put(name, value);
}
}
return map;
}
Этот код преобразует это:
<house>
<door>blue</door>
<living-room>
<table>wood</table>
<chair>wood</chair>
</living-room>
</house>
в
{
"house": {
"door": "blue",
"living-room": {
"table": "wood",
"chair": "wood"
}
}
}
У меня нет обратного процесса, но писать его не так сложно.
Ответ 7
Я отправляю это как ответ не потому, что это правильный ответ на ваш вопрос, а потому, что он является решением одной и той же проблемы, но вместо этого использует атрибуты. В противном случае ответ Викаса Гуджжара правильный.
Довольно часто ваши данные могут быть в атрибутах, но довольно сложно найти какие-либо рабочие примеры с помощью XStream, чтобы сделать это, так что вот один из них:
Пример данных:
<settings>
<property name="prop1" value="foo"/>
<property name="prop2" /> <!-- NOTE:
The example supports null elements as
the backing object is a HashMap.
A Properties object would be handled
by a PropertiesConverter which wouldn't
allow you null values. -->
<property name="prop3" value="1"/>
</settings>
Реализация MapEntryConverter (слегка переработанная реализация @Vikas Gujjar для использования атрибутов):
public class MapEntryConverter
implements Converter
{
public boolean canConvert(Class clazz)
{
return AbstractMap.class.isAssignableFrom(clazz);
}
public void marshal(Object value,
HierarchicalStreamWriter writer,
MarshallingContext context)
{
//noinspection unchecked
AbstractMap<String, String> map = (AbstractMap<String, String>) value;
for (Map.Entry<String, String> entry : map.entrySet())
{
//noinspection RedundantStringToString
writer.startNode(entry.getKey().toString());
//noinspection RedundantStringToString
writer.setValue(entry.getValue().toString());
writer.endNode();
}
}
public Object unmarshal(HierarchicalStreamReader reader,
UnmarshallingContext context)
{
Map<String, String> map = new HashMap<String, String>();
while (reader.hasMoreChildren())
{
reader.moveDown();
map.put(reader.getAttribute("name"), reader.getAttribute("value"));
reader.moveUp();
}
return map;
}
}
Настройка, разбор и хранение экземпляров XStream:
XStream xstream = new XStream();
xstream.autodetectAnnotations(true);
xstream.alias("settings", HashMap.class);
xstream.registerConverter(new MapEntryConverter());
...
// Parse:
YourObject yourObject = (YourObject) xstream.fromXML(is);
// Store:
xstream.toXML(yourObject);
...
Ответ 8
Я нашел это в google, но я не хочу использовать XStream, потому что это вызывает много накладных расходов в моей среде. Мне только нужно было проанализировать файл, и поскольку я не нашел ничего, что мне нравится, я создал свое собственное простое решение для разбора файла формата, который вы описываете. Итак, вот мое решение:
public class XmlToMapUtil {
public static Map<String, String> parse(InputSource inputSource) throws SAXException, IOException, ParserConfigurationException {
final DataCollector handler = new DataCollector();
SAXParserFactory.newInstance().newSAXParser().parse(inputSource, handler);
return handler.result;
}
private static class DataCollector extends DefaultHandler {
private final StringBuilder buffer = new StringBuilder();
private final Map<String, String> result = new HashMap<String, String>();
@Override
public void endElement(String uri, String localName, String qName) throws SAXException {
final String value = buffer.toString().trim();
if (value.length() > 0) {
result.put(qName, value);
}
buffer.setLength(0);
}
@Override
public void characters(char[] ch, int start, int length) throws SAXException {
buffer.append(ch, start, length);
}
}
}
Вот пара тестов TestNG + FEST Assert:
public class XmlToMapUtilTest {
@Test(dataProvider = "provide_xml_entries")
public void parse_returnsMapFromXml(String xml, MapAssert.Entry[] entries) throws Exception {
// execution
final Map<String, String> actual = XmlToMapUtil.parse(new InputSource(new StringReader(xml)));
// evaluation
assertThat(actual)
.includes(entries)
.hasSize(entries.length);
}
@DataProvider
public Object[][] provide_xml_entries() {
return new Object[][]{
{"<root />", new MapAssert.Entry[0]},
{
"<root><a>aVal</a></root>", new MapAssert.Entry[]{
MapAssert.entry("a", "aVal")
},
},
{
"<root><a>aVal</a><b>bVal</b></root>", new MapAssert.Entry[]{
MapAssert.entry("a", "aVal"),
MapAssert.entry("b", "bVal")
},
},
{
"<root> \t <a>\taVal </a><b /></root>", new MapAssert.Entry[]{
MapAssert.entry("a", "aVal")
},
},
};
}
}
Ответ 9
Я пробовал разные типы карт и работал Конверсионный ящик. Я использовал вашу карту и вставил пример ниже с некоторыми внутренними картами. Надеюсь, это полезно для вас....
import java.util.HashMap;
import java.util.Map;
import cjm.component.cb.map.ToMap;
import cjm.component.cb.xml.ToXML;
public class Testing
{
public static void main(String[] args)
{
try
{
Map<String, Object> map = new HashMap<String, Object>(); // ORIGINAL MAP
map.put("name", "chris");
map.put("island", "faranga");
Map<String, String> mapInner = new HashMap<String, String>(); // SAMPLE INNER MAP
mapInner.put("a", "A");
mapInner.put("b", "B");
mapInner.put("c", "C");
map.put("innerMap", mapInner);
Map<String, Object> mapRoot = new HashMap<String, Object>(); // ROOT MAP
mapRoot.put("ROOT", map);
System.out.println("Map: " + mapRoot);
System.out.println();
ToXML toXML = new ToXML();
String convertedXML = String.valueOf(toXML.convertToXML(mapRoot, true)); // CONVERTING ROOT MAP TO XML
System.out.println("Converted XML: " + convertedXML);
System.out.println();
ToMap toMap = new ToMap();
Map<String, Object> convertedMap = toMap.convertToMap(convertedXML); // CONVERTING CONVERTED XML BACK TO MAP
System.out.println("Converted Map: " + convertedMap);
}
catch (Exception e)
{
e.printStackTrace();
}
}
}
Вывод:
Map: {ROOT={name=chris, innerMap={b=B, c=C, a=A}, island=faranga}}
-------- Map Detected --------
-------- XML created Successfully --------
Converted XML: <ROOT><name>chris</name><innerMap><b>B</b><c>C</c><a>A</a></innerMap><island>faranga</island></ROOT>
-------- XML Detected --------
-------- Map created Successfully --------
Converted Map: {ROOT={name=chris, innerMap={b=B, c=C, a=A}, island=faranga}}
Ответ 10
Библиотека Underscore-java может конвертировать Map в xml. Я поддерживаю проект. Живой пример
Пример кода:
import com.github.underscore.lodash.U;
import java.util.*;
public class Main {
public static void main(String[] args) {
Map<String, Object> map = new LinkedHashMap<String, Object>();
map.put("name", "chris");
map.put("island", "faranga");
System.out.println(U.toXml(map));
// <?xml version="1.0" encoding="UTF-8"?>
// <root>
// <name>chris</name>
// <island>faranga</island>
// </root>
// and back:
map = (Map<String, Object>) U.fromXml("<?xml version=\"1.0\" encoding=\"UTF-8\"?><root>"
+ " <name>chris</name>"
+ " <island>faranga</island>"
+ " </root>");
System.out.println(map);
// {name=chris, island=faranga}
}
}
Ответ 11
Теперь это 2017. Последняя версия XStream требует, чтобы конвертер работал как ожидаемый.
Конвертор поддерживает вложенную карту:
public class MapEntryConverter implements Converter {
@Override
public void marshal(Object value, HierarchicalStreamWriter writer, MarshallingContext marshallingContext) {
AbstractMap map = (AbstractMap) value;
for (Object obj : map.entrySet()) {
Map.Entry entry = (Map.Entry) obj;
writer.startNode(entry.getKey().toString());
Object val = entry.getValue();
if (val instanceof Map) {
marshal(val, writer, marshallingContext);
} else if (null != val) {
writer.setValue(val.toString());
}
writer.endNode();
}
}
@Override
public Object unmarshal(HierarchicalStreamReader reader, UnmarshallingContext unmarshallingContext) {
Map<String, Object> map = new HashMap<>();
while(reader.hasMoreChildren()) {
reader.moveDown();
String key = reader.getNodeName(); // nodeName aka element name
String value = reader.getValue().replaceAll("\\n|\\t", "");
if (StringUtils.isBlank(value)) {
map.put(key, unmarshal(reader, unmarshallingContext));
} else {
map.put(key, value);
}
reader.moveUp();
}
return map;
}
@Override
public boolean canConvert(Class clazz) {
return AbstractMap.class.isAssignableFrom(clazz);
}
}
Ответ 12
В моем случае я конвертирую DBresponse в XML в Camel ctx. Исполнитель JDBC возвращает ArrayList (строки) с LinkedCaseInsensitiveMap (одна строка). Задача - создать объект XML на основе DBResponce.
import java.io.StringWriter;
import java.util.ArrayList;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.transform.OutputKeys;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;
import org.springframework.util.LinkedCaseInsensitiveMap;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
public class ConvertDBToXMLProcessor implements Processor {
public void process(List body) {
if (body instanceof ArrayList) {
ArrayList<LinkedCaseInsensitiveMap> rows = (ArrayList) body;
DocumentBuilder builder = null;
builder = DocumentBuilderFactory.newInstance().newDocumentBuilder();
Document document = builder.newDocument();
Element rootElement = document.createElement("DBResultSet");
for (LinkedCaseInsensitiveMap row : rows) {
Element newNode = document.createElement("Row");
row.forEach((key, value) -> {
if (value != null) {
Element newKey = document.createElement((String) key);
newKey.setTextContent(value.toString());
newNode.appendChild(newKey);
}
});
rootElement.appendChild(newNode);
}
document.appendChild(rootElement);
/*
* If you need return string view instead org.w3c.dom.Document
*/
StringWriter writer = new StringWriter();
StreamResult result = new StreamResult(writer);
DOMSource domSource = new DOMSource(document);
TransformerFactory tf = TransformerFactory.newInstance();
Transformer transformer = tf.newTransformer();
transformer.setOutputProperty(OutputKeys.INDENT, "yes");
transformer.setOutputProperty(OutputKeys.OMIT_XML_DECLARATION, "no");
transformer.transform(domSource, result);
// return document
// return writer.toString()
}
}
}