简体   繁体   中英

Convert XML to Map of nested maps

I have an XML with nested elements and repeating tags. For example:

<person>
    <name>Rama</name>
    <age>27</age>
    <gender>male</gender>
    <address>
        <doornumber>234</doornumber>
        <street>Kanon</street>
        <city>Hyderabad</city>
    </address>
    <qualification>
        <degree>M.Sc</degree>
        <specialisation>Maths</specialisation>
    </qualification>
    <qualification>
        <degree>B.E.</degree>
        <specialisation>Electrical</specialisation>
    </qualification>
</person>

Now I want an API which will convert this XML into a Map of Maps in Java:

{name="Rama",age="27",gender="male",address={doornumber=234,street="Kanon",city="Hyderabad"},qualification=[{degree="M.Sc",specialisation="Maths"},{degree="B.E.",specialisation="Electrical"}]}

I know that we can use XStream API to achieve this. Here I just wanted to know if using XStream has any downsides and whether there exists any other better Java API for achieving this. Any suggestions?

Note: This should be done in a generic way ie the Java API should be applicable to any XML, not just to the above XML.

It's very late though. But I've written a custom MapEntryConverter for XStream API which can handly any complexity of XML data, no matter how deep it is. Even it'll support Duplicate tag names (which will be stored as ArrayList).

XStream xStream = new XStream(new DomDriver());
xStream.registerConverter(new MapEntryConverter());
xStream.alias("xml", java.util.Map.class);

// from XML, convert back to map
Map<String, List<Object>> map = (Map<String, List<Object>>) xStream.fromXML(xmlData);
/*System.out.println("MAP: \n" + map.entrySet().toString());*/

String xml = xStream.toXML(map);
/*System.out.println("XML: \n"+xml);*/

MapEntryConverter.java

import java.util.AbstractMap;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;

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;

public class MapEntryConverter implements Converter{
    @SuppressWarnings("rawtypes")
    public boolean canConvert(Class clazz) {
        return AbstractMap.class.isAssignableFrom(clazz);
    }

    @SuppressWarnings({ "unchecked" })
    public void marshal(Object value, HierarchicalStreamWriter writer, MarshallingContext context) {
        AbstractMap<String, List<?>> map = (AbstractMap<String, List<?>>) value;
        List<Map<String, ?>> list = (List<Map<String, ?>>) map.get("xml");
        for( Map<String, ?> maps: list ) {
            for( Entry<String, ?> entry: maps.entrySet() ) {
                mapToXML(writer, entry);
            }
        }
    }

    @SuppressWarnings("unchecked")
    private void mapToXML(HierarchicalStreamWriter writer, Entry<String, ?> entry) {
        writer.startNode(entry.getKey());
        if( entry.getValue() instanceof String ) {
            writer.setValue(entry.getValue().toString());
        }else if(  entry.getValue() instanceof ArrayList ) {
            List<?> list = (List<?>) entry.getValue();
            for( Object object: list ) {
                Map<String, ?> map = (Map<String, ?>) object;
                for( Entry<String, ?> entryS: map.entrySet() ) {
                    mapToXML(writer, entryS);
                }
            }
        }
        writer.endNode();
    }

    public Object unmarshal(HierarchicalStreamReader reader, UnmarshallingContext context) {
        Map<String, List<Object>> map = new HashMap<String, List<Object>>();
        map = xmlToMap(reader, new HashMap<String, List<Object>>());
        return map;
    }

    private Map<String, List<Object>> xmlToMap(HierarchicalStreamReader reader, Map<String, List<Object>> map) {
        List<Object> list = new ArrayList<Object>();
        while(reader.hasMoreChildren()) {
            reader.moveDown();
            if( reader.hasMoreChildren() ) {
                list.add(xmlToMap(reader, new HashMap<String, List<Object>>()));
            }else {
                Map<String, Object> mapN = new HashMap<String, Object>();
                mapN.put(reader.getNodeName(), reader.getValue());
                list.add(mapN);
            }
            reader.moveUp();
        }
        map.put(reader.getNodeName(), list);
        return map;
    }

}

Do note that each value in Map is stored as a list if it has any children. The unmarshalling part is quite neat according to me. I'll polish and simplify the marshalling part in free time.

Input data should be provided within xml tag like below.

<xml>
    <person>
        <name>Rama</name>
        <age>27</age>
        <gender>male</gender>
        <address>
            <doornumber>234</doornumber>
            <street>Kanon</street>
            <city>Hyderabad</city>
        </address>
        <qualification>
            <degree>M.Sc</degree>
            <specialisation>Maths</specialisation>
        </qualification>
        <qualification>
            <degree>B.E.</degree>
            <specialisation>Electrical</specialisation>
        </qualification>
    </person>
</xml>

There is a little change when comparing my output with your expected output. Duplicate key values are not merged as an array, instead they're of seperate list.

I used https://github.com/douglascrockford/JSON-java this project for once to convert xml to json. it is fast and does its job.

就个人而言,我会使用标准的JAXP接口 - 如果你真的想要一张地图的地图,那就是SAX,如果你想保留顺序的DOM(一棵树而不是无序的嵌套地图),并且想要更好地映射到XML信息集的API。

I think Melih might have been referring to this solution. Here is some Kotlin code using the json.org library:

Add implementation "org.json:json:20180130" to build.gradle .

import org.json.JSONML

fun xmlToMap(xml: String): MutableMap<String, Any>? {
    return JSONML.toJSONObject(xml).toMap()
}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

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