JAXB, Custom XML to Java Map using XMLAdapter

In some cases XML structure that is specific to a Java container is very different, e.g. Map. And we want to bind our Java classes with a customized and more readable XML, JAXB XmlJavaTypeAdapter annotation comes very handy in such situations. Such mappings require an adapter class, written as an extension of XmlAdapter, and XmlJavaTypeAdapter annotation suggests JAXB to use the adapter at specified location:

For illustration purpose, we’ll have a custom XML:

<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<profile>
	<messages>
		<message id="1">
			<subject>hi</subject>
			<body>wat's up mike.. r u gonna catch us tonight?</body>
		</message>
		<message id="2">
			<subject>re:hi</subject>
			<body>My apologies, forgot to tell ya, I'm out of town!!!</body>
		</message>
	</messages>
</profile>

And this is our JAXB annotated message class(minimal):


public class Message {
	@XmlAttribute
	private String id;
	@XmlElement
	private String subject;
	@XmlElement
	private String body;
}

And the profile class with required annotations, note that we have a HashMap of Message where message id is the key and Message object itself as a value. Also note the use of @XmlJavaTypeAdapter which provides mapping between custom XML and HashMap during the process of marshaling/ unmarshaling. Going forward we’ll see how we are going to achieve that.

@XmlRootElement(name="profile")
public class Profile {
    @XmlElement
    @XmlJavaTypeAdapter(MessageAdapter.class)
    private HashMap<String, Message> messages;
    
    public Profile(){}
    public Profile(HashMap<String, Message> b ){
    	messages = b;
    }
}

To fill the gap we need to provide something that JAXB knows how to handle, we can use a wrapper class which contains Array of objects of type Messagse.

public class Messages {
    @XmlElement(name="message")
    public Message[] messages;
}

Now you must have got a fair idea that how JAXB is going to read XML messages to Message array back and forth. Let’s write an adapter to map our Array to a HasMap.

public class MessageAdapter extends XmlAdapter<Messages,Map<String, Message>> {
    @Override
    public Map<String, Message> unmarshal( Messages value ){
    	Map<String, Message> map = new HashMap<String, Message>();
        for( Message msg : value.messages )
            map.put( msg.getId(), msg );
        return map;
    }  

    @Override
    public Messages marshal( Map<String, Message> map ){
        Messages msgCont = new Messages();
        Collection<Message> msgs = map.values();
        msgCont.messages = msgs.toArray(new Message[msgs.size()]);
        return msgCont;
    }
}

And here is the test class to assert our assumptions:

public class XmlAdapterTest extends TestCase{
	
	public void testAdapter() throws Exception {
		InputStream is = this.getClass().getClassLoader().getResourceAsStream("profile.xml");
		if (is != null) {
			JAXBContext jc;
			try {
				//test unmarshaling
				jc = JAXBContext.newInstance(Profile.class.getPackage().getName());
				Unmarshaller u = jc.createUnmarshaller();
				Profile profile = (Profile) u.unmarshal(is);
				assertNotNull(profile.getMessages());
				assertEquals( 2, profile.getMessages().size());
				
				//test marshaling
				Marshaller marshaller=jc.createMarshaller();
				File xmlDocument = new File("output.xml");
				marshaller.marshal(profile, new FileOutputStream(xmlDocument));
				assertTrue(xmlDocument.length() > 0);
				xmlDocument.delete();
			} catch (JAXBException e) {
				e.printStackTrace();
				fail();
			}
		}
	}
}

Still need the source code, find here: jaxb-typeadapter source, Sushant

Advertisements

About sushant
Tech enthusiast working on java/j2ee, loves to explore as well as stay focused with core programming.

18 Responses to JAXB, Custom XML to Java Map using XMLAdapter

  1. Muhammad says:

    well thank for the idea but i want a case where i will create an XML of such
    [the tag theQuestion is more than one that is what i want but …. can u pls send me mail so that u wil help me]

    xzcdhtrr

    asdasd
    csac
    cvfwefwe
    adsadad
    ertert
    ertert
    ertret
    ewtert

  2. Hi Muhammad,

    Can you post proper XML using help provided here http://en.support.wordpress.com/code/posting-source-code
    .
    Also give little meaningful names to your XML tags. What I’ve understood out of your question is you have several “ertert” tags, same way I had several “message” tags. Is it that, you don’t want to wrap ’em the way I wrapped under “messages”?

    Please post proper xml with meaningful names.

  3. Muhammad says:

    Thanks am working on it but in my case i use XML schemas to generate the JAVA CLASSES.am sure ur code will help thanks

  4. yogesh says:

    Hi Sushant.. thx for this post.. its truly helpful for me. I am having few doubts like… in my previous project I was parsing XML file, size was around 40 to 50 MB and it was containing around 1lakh rows to insert data into DB. Would this tool works fine with larger records like 100000 rows??. Thanks in advance…:)

  5. anandparthasarathy says:

    Hi Sushant,

    Thanks for the post. But I need an XML which will be something like this:

    555

    123.45
    12345
    card
    Q

    123.45
    2333
    cash
    Q

    As you see, the elements in the map are not the same for each entry. Could you please let me know how this can be achieved using JAXB?

    Regards,
    -Anand

  6. anandparthasarathy says:

    Sorry once again. WordPress does not have a preview and I do not how the source code shows up until I post the comment.

    <?xml version="1.0" encoding="UTF-8" standalone="yes"?>
    <cart>
        <supervisor_id>555</supervisor_id>
        <payments>
            <payment sequence="1">
    			<amount>123.45</amount>
                <billing_method>12345</billing_method>
                <form>card</form>
                <delivery_mode>Q</delivery_mode>
            </payment>
    		<payment sequence="2">
    			<amount>123.45</amount>
                <person_id>2333</person_id>
                <form>cash</form>
                <delivery_mode>Q</delivery_mode>
            </payment>
    	</payments>
    </cart>
    

    -Anand

  7. Anand,

    As I see your Payment class is having a potential candidate “sequence” to be key of the MAP. I assume Payment class will have optional members like “billing_method” or “person_id”, if this is the case you can implement that easily.

    Or if you want to have a MAP of key-value pair, populate your map in a class which is extending the XmlAdapter the way you want. Still you will have to have Payment class which will have all possible annotated members coresponding to elements in XML.

  8. devcodeur says:

    I would like to do something like this (without the messages tag), do you know how to achieve this ?

    <?xml version="1.0" encoding="UTF-8" standalone="yes"?>
    <profile>
    	<message id="1">
    		<subject>hi</subject>
    		<body>wat's up mike.. r u gonna catch us tonight?</body>
    	</message>
    	<message id="2">
    		<subject>re:hi</subject>
    		<body>My apologies, forgot to tell ya, I'm out of town!!!</body>
    	</message>
    </profile>
    
    • Hi Dev.

      By definition XMLAdapter applies on following target:
      @Target(value={FIELD, METHOD, PACKAGE, PARAMETER, TYPE})

      So we need to have a property (here messages) under root element (profile) at our class level.
      Alternatively you may have an array of Message object as property of Profile and that can be populated without using XMLAdapter.

      If you need further investigation, I may try to get some time out over the coming weekend.

      ..
      Sushant

      • Markus says:

        Hi Sushant, thx for your useful example

        I’m having the same issue as devcodeur. I’ve tried every example I could find – additionally to just reading the docs and trying to code it myself: I have a list of elements in my XML and would like to map them to a map(string-key, element-class) in my code. Using the XmlAdapter I cannot get rid of the surrounding element (like devcodeur), which is named like my annotation (name=x) – the elements themselves are named “item”. And I’m returning a primitive array typed as my domain class from the XmlAdapter. Do you have an idea what’s wrong here?

        Thx, Markus

      • Markus,

        I am really unable to visualize your case, if problem is the same as Devcodeur’s, you already have an answer.

        Thanks,
        Sushant

  9. Ravi says:

    HiSushanth,
    We have requirement to accept custom java bean as a parameter and also collection type as parameter for a restful webservices.Can you please point to me on how to do this?

    Thanks in advance

    Ravi

  10. Hari says:

    sushant, cd you pls fwd me the basic src code zip used in this article [just to get an xml loaded as hashmap]

  11. matthias says:

    … simply great!!! That’s just what I needed – and it could be simply adapted to similar cases, where generic maps are to be serialized to a nice readable xml!

  12. Pingback: extensible XmlAdapter for Map bound types | jinahya

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

%d bloggers like this: