繁体   English   中英

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

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

我正在搜索一个轻量级 API(最好是单个类)来转换

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

到 XML,反之亦然,将 XML 转换回Map<String,String>

例子:

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

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


我创建了一个工作复制和粘贴示例。 感谢fvuMichal Bernhard

下载最新的 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-xyzjar就足够了。

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

                reader.moveUp();
            }

            return map;
        }

    }

}

这里是 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;
}

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

一种选择是推出自己的。 这样做相当简单:

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 Gods 要求的样板文件,但它最多需要 1 个小时的工作。

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

我使用自定义转换器的方法:

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。 这应该会改进适用于复合地图值和嵌套地图的解决方案。

我在谷歌上找到了这个,但我不想使用 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 断言测试:

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

我编写了一段代码,将 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"
        }
     }
 }

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

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

我将此作为答案发布,不是因为它是您问题的正确答案,而是因为它是同一问题的解决方案,而是使用属性。 否则 Vikas Gujjar 的答案是正确的。

很多时候你的数据可能在属性中,但很难找到任何使用 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);
    ...

我尝试了不同类型的地图,并且转换框有效。 我已经使用了您的地图并在下面粘贴了一个带有一些内部地图的示例。 希望对你有帮助......

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

现在是 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'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);
    }
} 

如果你只需要将简单的映射转换为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();
}

就我而言,我在 Camel ctx 中将 DBresponse 转换为 XML。 JDBC 执行器返回带有 LinkedCaseInsensitiveMap(单行)的 ArrayList(行)。 任务 - 基于 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