Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

JAXB unmarshal elements of XML to Map

Tags:

java

xml

jaxb

I have an XML file. In this file, some of the elements are have attributes that change. I want to put those attributes into a Map. How do I do this?

My XML is:

<ROW id='1'>
    <MOBILE>9831138683</MOBILE>
    <VARS>
        <CAUSE>Delayed payment</CAUSE>
        <DO>100.56</DO>
        <LOT>1</LOT>
    </VARS>
</ROW>
<ROW id='2'>
    <MOBILE>9831138684</MOBILE>
    <VARS>
        <NAME>hi</NAME>
        <ADDRESS>Here</ADDRESS>
        <LOT>2</LOT>
    </VARS>
</ROW>

In this, the VARS element can have attributes which changes and I do not know beforehand what these elements will be.

I have created a class for this purpose:

@XmlRootElement(name = "ROW")
@XmlAccessorType(XmlAccessType.FIELD)
public class SMSDetail {
    @XmlAttribute
    private int id;
    @XmlElement(name = "MOBILE")
    private int mobileNo;
    @XmlElement(name = "VARS")
    @XmlJavaTypeAdapter(MapAdapter.class)
    private HashMap<String, String> variableMap;

    public int getId() {
        return id;
    }

    public int getMobileNo() {
        return mobileNo;
    }

    public HashMap<String, String> getVariableMap() {
        return variableMap;
    }

    public void setId(int id) {
        this.id = id;
    }

    public void setMobileNo(int mobileNo) {
        this.mobileNo = mobileNo;
    }

    public void setVariableMap(HashMap<String, String> variableMap) {
        this.variableMap = variableMap;
    }
}

I want to map the VARS element to a Map. I want the tags such as CAUSE, LOT to be keys and their values to be the values in the map. I have written an XmlAdapater for this purpose:

public class MapAdapter extends XmlAdapter<MapElements[], Map<String, String>> {
    public MapAdapter() {
    }

    @Override
    public MapElements[] marshal(Map<String, String> arg0) throws Exception {
        MapElements[] mapElements = new MapElements[arg0.size()];
        int i = 0;
        for (Map.Entry<String, String> entry : arg0.entrySet())
            mapElements[i++] = new MapElements(entry.getKey(), entry.getValue());

        return mapElements;
    }

    @Override
    public Map<String, String> unmarshal(MapElements[] arg0) throws Exception {
        Map<String, String> r = new TreeMap<String, String>();
        for (MapElements mapelement : arg0)
            r.put(mapelement.key, mapelement.value);
        return r;
    }
}

class MapElements {
    @XmlAttribute
    public String key;
    @XmlAttribute
    public String value;

    private MapElements() {
    } //Required by JAXB

    public MapElements(String key, String value) {
        this.key = key;
        this.value = value;
    }
}

This adapter is giving me null for the variableMap variable. How should the Adapter be modified for this?

like image 351
khateeb Avatar asked Nov 28 '14 06:11

khateeb


People also ask

How do you Unmarshal a list of objects?

public List<T> Unmarshal(List<Entry> entries, Class clazz) { List<T> out = new ArrayList<T>(); T instance; for (Entry e : entries) { try { JAXBContext context = JAXBContext. newInstance(clazz); Unmarshaller unmarsh = context.

How do you Unmarshal with JAXB?

UNMARSHALLING. To unmarshal an xml string into a JAXB object, you will need to create an Unmarshaller from the JAXBContext, then call the unmarshal() method with a source/reader and the expected root object.

How does JAXB marshalling work?

In JAXB, marshalling involves parsing an XML content object tree and writing out an XML document that is an accurate representation of the original XML document, and is valid with respect the source schema. JAXB can marshal XML data to XML documents, SAX content handlers, and DOM nodes.


1 Answers

You could do the following:

XmlAdapter (MapAdapter)

You could do the following for your XmlAdapter where you convert an instance of Map to an object that has a List of DOM Element. You will construct the instances of Element so that the name is they key from the map entry, and the text content is the value.

import java.util.*;
import java.util.Map.Entry;

import javax.xml.bind.annotation.XmlAnyElement;
import javax.xml.bind.annotation.adapters.XmlAdapter;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;

import org.w3c.dom.*;

public class MapAdapter extends XmlAdapter<MapAdapter.AdaptedMap, Map<String, String>> {

    private DocumentBuilder documentBuilder;

    public MapAdapter() throws Exception {
        documentBuilder = DocumentBuilderFactory.newInstance().newDocumentBuilder();
    }

    public static class AdaptedMap {        
        @XmlAnyElement
        public List<Element> elements = new ArrayList<Element>();
    }

    @Override
    public AdaptedMap marshal(Map<String, String> map) throws Exception {
        Document document = documentBuilder.newDocument();
        AdaptedMap adaptedMap = new AdaptedMap();
        for(Entry<String, String> entry : map.entrySet()) {
            Element element = document.createElement(entry.getKey());
            element.setTextContent(entry.getValue());
            adaptedMap.elements.add(element);
        }
        return adaptedMap;
    }

    @Override
    public Map<String, String> unmarshal(AdaptedMap adaptedMap) throws Exception {
        HashMap<String, String> map = new HashMap<String, String>();
        for(Element element : adaptedMap.elements) {
            map.put(element.getLocalName(), element.getTextContent());
        }
        return map;
    }


}

Optimizing the Use of MapAdapter

To improve performance we want to minimize the number of times the DocumentBuiderFactory and DocumentBuilder is instantiated. We can do this by creating an instance of the MapAdapter for JAXB to use and set it on the Marshaller and Unmarshaller. This way JAXB will use that instance instead of creating a new one each time the adapter is required.

import java.io.File;
import javax.xml.bind.*;

public class Demo {

    public static void main(String[] args) throws Exception {
        JAXBContext jc = JAXBContext.newInstance(SMSDetail.class);

        MapAdapter mapAdapter = new MapAdapter();

        Unmarshaller unmarshaller = jc.createUnmarshaller();
        unmarshaller.setAdapter(mapAdapter);
        File xml = new File("src/forum27182975/input.xml");
        SMSDetail smsDetail = (SMSDetail) unmarshaller.unmarshal(xml);

        Marshaller marshaller = jc.createMarshaller();
        marshaller.setAdapter(mapAdapter);
        marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true);
        marshaller.marshal(smsDetail, System.out);
    }

}

If You are Using MOXy as your JAXB (JSR-222) Provider

If you are using MOXy as your JAXB provider then you can leverage the @XmlVariableNode extension to make the mapping of this use case easier:

  • http://blog.bdoughan.com/2013/06/moxys-xmlvariablenode-using-maps-key-as.html
like image 174
bdoughan Avatar answered Oct 09 '22 22:10

bdoughan