简体   繁体   English

如何将 XML 转换为 java.util.Map,反之亦然?

[英]How to convert XML to java.util.Map and vice versa?

I'm searching a lightweight API (preferable single class) to convert a我正在搜索一个轻量级 API(最好是单个类)来转换

Map<String,String> map = new HashMap<String,String();

to XML and, vice versa, convert the XML back to a Map<String,String> .到 XML,反之亦然,将 XML 转换回Map<String,String>

example:例子:

Map<String,String> map = new HashMap<String,String();
map.put("name","chris");
map.put("island","faranga");

MagicAPI.toXML(map,"root");

result:结果:

<root>
  <name>chris</chris>
  <island>faranga</island>
</root>

and back:然后回来:

Map<String,String> map = MagicAPI.fromXML("...");

I don't want to use JAXB or JSON conversion API .我不想使用JAXBJSON 转换 API It doesn't have to take care of nested maps or attributes or anything else, just that simple case.它不必处理嵌套的映射或属性或其他任何东西,只是这种简单的情况。 Any suggestions?有什么建议么?


I created a working copy & paste sample.我创建了一个工作复制和粘贴示例。 Thanks to fvu and Michal Bernhard .感谢fvuMichal Bernhard

Download latest XStream framework , 'core only' is enough.下载最新的 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);

No converters or anything else is required.不需要转换器或其他任何东西。 Just the xstream-xyzjar is enough.只需xstream-xyzjar就足够了。

XStream! XStream!

Updated : I added unmarshal part as requested in comments..更新:我按照评论中的要求添加了解组部分。

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's name
                String value = reader.getValue();
                map.put(key, value);

                reader.moveUp();
            }

            return map;
        }

    }

}

Here the converter for XStream including unmarshall这里是 XStream 的转换器,包括解组

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;
}

How about XStream ? XStream怎么样? Not 1 class but 2 jars for many use cases including yours, very simple to use yet quite powerful.对于包括您在内的许多用例,不是 1 个类而是 2 个 jar,使用起来非常简单但功能非常强大。

One option would be to roll your own.一种选择是推出自己的。 It would be fairly simple to do:这样做相当简单:

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);

and the load is an equally simply getChildNodes and a loop.并且负载是一个同样简单的getChildNodes和一个循环。 Sure it has a bit of boiler plate that the XML Gods demand but it is at most 1 hours work.当然它有一些 XML Gods 要求的样板文件,但它最多需要 1 个小时的工作。

Or you could look at Properties if you are not too fused about the format of the XML.或者,如果您不太了解 XML 的格式,则可以查看属性

I used the approach with the custom converter:我使用自定义转换器的方法:

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 ;)
    }

}

But i changed the the serialization of the maps value to delegate to the MarshallingContext.但是我将映射值的序列化更改为委托给 MarshallingContext。 This should improve the solution to work for composite map values and nested maps as well.这应该会改进适用于复合地图值和嵌套地图的解决方案。

I found this on google, but I don't want to use XStream, because it causes to much overhead in my environment.我在谷歌上找到了这个,但我不想使用 XStream,因为它会在我的环境中造成很大的开销。 I only needed to parse a file and since I did not find anything I like I created my own simple solution for parsing a file of the format that you describe.我只需要解析一个文件,因为我没有找到任何我喜欢的东西,所以我创建了自己的简单解决方案来解析您描述的格式的文件。 So here is my solution:所以这是我的解决方案:

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);
        }
    }
}

Here are a couple of TestNG+FEST Assert Tests:以下是几个 TestNG+FEST 断言测试:

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")
                    },
                },
        };
    }
}

I have written a piece of code that transforms a XML content into a multi-layer structure of maps:我编写了一段代码,将 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;
}

This code transforms this:此代码对此进行了转换:

<house>
    <door>blue</door>
    <living-room>
        <table>wood</table>
        <chair>wood</chair>
    </living-room>
</house>

into进入

{
    "house": {
        "door": "blue",
        "living-room": {
            "table": "wood",
            "chair": "wood"
        }
     }
 }

I don't have the inverse process, but that must not be very difficult to write.我没有逆过程,但这一定不是很难写。

Underscore-java library can convert Map to xml. Underscore-java库可以将 Map 转换为 xml。 Live example活生生的例子

Code example:代码示例:

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 = U.fromXmlMap("<?xml version=\"1.0\" encoding=\"UTF-8\"?><root>"
        + "    <name>chris</name>"
        + "    <island>faranga</island>"
        + "  </root>");
        
    System.out.println(map);
    // {name=chris, island=faranga}
  }
}

I am posting this as an answer not because it's the correct answer to your question, but because it's a solution to the same problem, but using attributes instead.我将此作为答案发布,不是因为它是您问题的正确答案,而是因为它是同一问题的解决方案,而是使用属性。 Otherwise Vikas Gujjar's answer is correct.否则 Vikas Gujjar 的答案是正确的。

Quite oftern your data could be in attributes, but it's quite hard to find any working examples using XStream to do this, so here's one:很多时候你的数据可能在属性中,但很难找到任何使用 XStream 来做到这一点的工作示例,所以这里有一个:

Sample data:样本数据:

<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>

Implementation of MapEntryConverter (slightly re-worked @Vikas Gujjar's implementation to use attributes instead): 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 instance setup, parsing and storing: 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);
    ...

I have tried different kinds of maps and the Conversion Box worked.我尝试了不同类型的地图,并且转换框有效。 I have used your map and have pasted an example below with some inner maps.我已经使用了您的地图并在下面粘贴了一个带有一些内部地图的示例。 Hope it is helpful to you ....希望对你有帮助......

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();
    }
}
}

Output:输出:

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}}

Now it's 2017, The latest version of XStream requires a converter to make it works as your expected.现在是 2017 年,最新版本的 XStream 需要一个转换器才能使其按预期工作。

A converter supports nested map:转换器支持嵌套映射:

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's 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);
    }
} 

If you only need to convert simple map to xml, without nested properties, then the lightweight solution would be just a private method, as follows:如果你只需要将简单的映射转换为xml,没有嵌套属性,那么轻量级的解决方案将只是一个私有方法,如下:

private String convertMapToXML(Map<String, String> map) {
    StringBuilder xmlBuilder = new StringBuilder();
    xmlBuilder.append("<xml>");
    for (Map.Entry<String, String> entry : map.entrySet()) {
        if (entry.getValue() != null) {
            String xmlElement = entry.getKey();
            xmlBuilder.append("<");
            xmlBuilder.append(xmlElement);
            xmlBuilder.append(">");
            xmlBuilder.append(entry.getValue());
            xmlBuilder.append("<");
            xmlBuilder.append("/");
            xmlBuilder.append(xmlElement);                
            xmlBuilder.append(">");
        }             
    }

    xmlBuilder.append("</xml>");
    return xmlBuilder.toString();
}

In my case I convert DBresponse to XML in Camel ctx.就我而言,我在 Camel ctx 中将 DBresponse 转换为 XML。 JDBC executor return the ArrayList (rows) with LinkedCaseInsensitiveMap (single row). JDBC 执行器返回带有 LinkedCaseInsensitiveMap(单行)的 ArrayList(行)。 Task - create XML object based on DBResponce.任务 - 基于 DBResponce 创建 XML 对象。

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()

        }
    }
}

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM