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

Posted

技术标签:

【中文标题】如何将 XML 转换为 java.util.Map,反之亦然?【英文标题】:How to convert XML to java.util.Map and vice versa? 【发布时间】:2010-12-04 23:09:24 【问题描述】:

我正在寻找一个轻量级的 API(最好是单个类)来转换一个

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

转换为 XML,反之亦然,将 XML 转换回Map&lt;String,String&gt;

示例:

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 或JSON conversion API。它不必处理嵌套映射或属性或其他任何东西,就那么简单的情况。 有什么建议吗?


我创建了一个工作副本和粘贴示例。感谢fvu 和Michal Bernhard。

Download latest XStream framework,“仅核心”就足够了。

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 就足够了。

【问题讨论】:

使用当前版本的 XStream,该示例生成 &lt;map&gt; &lt;entry&gt; &lt;string&gt;name&lt;/string&gt; &lt;string&gt;chris&lt;/string&gt; &lt;/entry&gt; &lt;entry&gt; &lt;string&gt;island&lt;/string&gt; &lt;string&gt;faranga&lt;/string&gt; &lt;/entry&gt; &lt;/map&gt; 好吧,我测试了从 1.2(旧版本反序列化/解组部分失败)到最新版本 1.4.6 的几个版本,并且总是需要像下面我的答案中那样的自定义地图转换器来生成输出想。否则,它会输出 Arjan 在上面的评论中所说的内容。 【参考方案1】:

XStream!

更新:我按照 cmets 的要求添加了 unmarshal 部分..

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 xs = new XStream(new DomDriver("UTF-8", new XmlFriendlyReplacer("_-", "_"))); // 正确处理下划线 我已经尝试了上面的代码,以防地图有域对象、字符串、布尔值等但没有转换为 xml,你能建议我做错了什么吗? 您好,非常感谢您的回复。我正在尝试解组XML,但我不知道如何在这里实现unmarshal 方法。如果可能的话,您能否提供MapAdapter 类中提到的unmarshal 方法的示例:***.com/a/32483762/7584240【参考方案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;

【讨论】:

伙计,我现在不得不写这个愚蠢的转换器 500 次。你会说我会学习! 每次我必须敲出一张更奇怪的地图,我最终会看到你的答案。如果可以的话,我会加十个你! ;)【参考方案3】:

一种选择是自己动手。这样做相当简单:

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 的格式,也可以查看 Properties。

【讨论】:

此函数是否对 xml 进行排序?我正在尝试构建一个函数来根据一个元素按字母顺序对 xml 进行排序【参考方案4】:

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

【讨论】:

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

【讨论】:

如果unmarshal() 是完整的,这将是一个更好的例子。【参考方案6】:

我在 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 断言测试:

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

【讨论】:

很好的答案,而且比 XStream 更轻。 XStream 还在使用 Robolectric 的 android 中创建版本错误,它也依赖于 XStream,而这个版本可以避免这种情况。请注意,此代码不会插入像&lt;empty /&gt; 这样的空节点,而 XStream 示例将它们作为空字符串插入。这可以使用startNode() 修改以保存第一个节点的名称,然后删除&gt; 0 检查。【参考方案7】:

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

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

【讨论】:

此代码不会生成 OP 所说的 Map Underscore-java 可以双向转换。只需使用方法 U.xmlToJson() 和 U.jsonToXml()。【参考方案8】:

Underscore-java 库可以将 Map 转换为 xml。 Live 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
  

【讨论】:

【参考方案9】:

我将其发布为答案不是因为它是您问题的正确答案,而是因为它是同一问题的解决方案,而是使用属性。否则 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);
    ...

【讨论】:

【参考方案10】:

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

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

【讨论】:

【参考方案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'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);
    
 

【讨论】:

这是最好的答案。但它不支持迭代【参考方案12】:

如果只需要简单的map转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();

【讨论】:

【参考方案13】:

在我的例子中,我在 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()

        
    

【讨论】:

以上是关于如何将 XML 转换为 java.util.Map,反之亦然?的主要内容,如果未能解决你的问题,请参考以下文章

java在后台如何将前台传过来的json格式数据转换为map?

Hibernate:如何将 java.util.Map 与 java.util.Set 映射为带有 LocalDate 的值?

将 Java 映射转换为 Scala 映射

Kafka Connect:如何将String解析为Map

Hive Generic UDF:Hive 未按预期进行转换,原因是:java.lang.ClassCastException:java.util.ArrayList 无法转换为 java.util.

map转实体工具类