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

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/1537207/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-12 13:57:33  来源:igfitidea点击:

How to convert XML to java.util.Map and vice versa

javaxml

提问by Chris

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.

到 xml,反之亦然,将 XML 转换回 Map。

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 dont want to use JAXBor JSON conversion API. It doesnt have to take care of nested maps or attributes or anything else, just that simple case. Any suggestions?

我不想使用JAXBJSON 转换 API。它不必处理嵌套的映射或属性或其他任何事情,就这么简单。有什么建议?



Edit: I created a working copy & paste sample. Thanks to fvuand 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-x.y.z.jaris enough.

不需要转换器或其他任何东西。只需xstream-xyzjar就足够了。

采纳答案by Michal Bernhard

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

    }

}

回答by fvu

How about XStream? Not 1 class but 2 jars for many use cases including yours, very simple to use yet quite powerful.

XStream怎么样?对于包括您在内的许多用例,不是 1 个类而是 2 个 jar,使用起来非常简单但功能非常强大。

回答by Michael Lloyd Lee mlk

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 getChildNodesand a loop. Sure it has a bit of boiler plate that the XML Gods demand but it is at most 1 hours work.

负载是一个同样简单getChildNodes的循环。当然它有一些 XML Gods 要求的样板文件,但它最多需要 1 个小时的工作。

Or you could look at Propertiesif you are not too fused about the format of the XML.

或者,如果您不太了解 XML 的格式,则可以查看属性

回答by Vikas Gujjar

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

回答by Eva Troels

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. This should improve the solution to work for composite map values and nested maps as well.

但是我将映射值的序列化更改为委托给 MarshallingContext。这应该会改进适用于复合地图值和嵌套地图的解决方案。

回答by carlspring

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

回答by yankee

I found this on google, but I don't want to use XStream, because it causes to much overhead in my environment. 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:

我在谷歌上找到了这个,但我不想使用 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);
        }
    }
}

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

回答by Jason Xavier

I have tried different kinds of maps and the Conversion Boxworked. 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}}

回答by mederel

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.

我没有逆过程,但这一定不是很难写。

回答by Valentyn Kolesnikov

Underscore-javalibrary can convert Map to xml. I am the maintainer of the project. Live example

Underscore-java库可以将 Map 转换为 xml。我是项目的维护者。活生生的例子

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