rawberto
rawberto

Reputation: 83

JAXB unmarshal XML elements to HashMap

I found a lot of articles that describe how to unmarshal a sequence of XML elements to a HashMap as long as they are within a "parent" element. However, I do not get this to work with the children directly under the root element!

Option 1 - Works:

<?xml version="1.0" encoding="UTF-8"?>
<checks>
  <checks>
    <check key="check1"/>
    <check key="check2"/>
    ...       
  </checks>
</checks>

Option 2 - Does not work:

<?xml version="1.0" encoding="UTF-8"?>
<checks>
  <check key="check1"/>
  <check key="check2"/>
  ...
</checks>

Checks:

package com.foo.conf;

import java.util.Map;

import javax.xml.bind.annotation.XmlElement;
import javax.xml.bind.annotation.XmlRootElement; 
import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter;

@XmlRootElement(name="checks")
public class Checks {       
    @XmlJavaTypeAdapter(ChecksAdapter.class)
    @XmlElement(name="checks")
    public Map<String, Check> checkMap;     
}

Check:

package com.foo.conf;

import javax.xml.bind.annotation.XmlAttribute;
import javax.xml.bind.annotation.XmlValue;

public class Check {
    @XmlAttribute public String key;
    @XmlValue public String description;

    public Check() { }

    public Check(String key) {
        this.key = key;
    }

    public String getCheckKey() {
        return this.key;
    }
}

CheckMapType:

package com.foo.conf;

import java.util.List;

import javax.xml.bind.annotation.XmlElement;

class CheckMapType { 
    @XmlElement(name="check")
    public List<Check> checkList; // = new ArrayList<Check>();
}

ChecksAdapter:

package com.foo.conf;

import java.util.HashMap;
import java.util.Map;

import javax.xml.bind.annotation.adapters.XmlAdapter;

final class ChecksAdapter extends XmlAdapter<CheckMapType, Map<String, Check>> {

    @Override
    public CheckMapType marshal(Map<String, Check> arg0) throws Exception {
        return null;
    }

    @Override
    public Map<String, Check> unmarshal(CheckMapType arg0) throws Exception {
        System.out.println("u: " + arg0.checkList.size());
        Map<String, Check> map = new HashMap<String, Check>();

        for (Check check : arg0.checkList) {
            System.out.println(check);
            map.put(check.key, check);
        }
        return map;
    }       
}

This is (some dummy test lineS) how I generate the classes/invoke the unmarshalling:

JAXBContext jc = JAXBContext.newInstance(Checks.class);
Unmarshaller u = jc.createUnmarshaller();
Checks c = (Checks) u.unmarshal(new File("checks.xml"));
System.out.println(c.checkMap.size());

Any idea on how to get option #2 to work? It works when using a List instead of the Map but I need the HashMap as I have to access the objects by the given keys...

Any hints much appreciated!

Upvotes: 2

Views: 9128

Answers (2)

bdoughan
bdoughan

Reputation: 148987

Note: I'm the EclipseLink JAXB (MOXy) lead and a member of the JAXB (JSR-222) expert group.

JAXB will treat each object relationship with a nesting relationship. Map is treated like an Object instead of a Collection so this is why you are getting the behaviour that you are seeing.

MOXy has an XPath based mapping extension called @XmlPath that could be used for this use case.

package com.foo.conf;

import java.util.Map;

import javax.xml.bind.annotation.XmlElement;
import javax.xml.bind.annotation.XmlRootElement; 
import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter;
import org.eclipse.persistence.oxm.annotations.XmlPath;

@XmlRootElement(name="checks")
public class Checks {       
    @XmlJavaTypeAdapter(ChecksAdapter.class)
    @XmlPath(".")
    public Map<String, Check> checkMap;     
}

For More Information

Upvotes: 3

user2720864
user2720864

Reputation: 8161

How are you generaing the JAXB classes? I am not sure what exactly are you trying to do but the below very simple code works for me ..

    JAXBContext jc = JAXBContext.newInstance(ChecksType.class);

    Unmarshaller unmarshaller = jc.createUnmarshaller();
    ChecksType chksType = (ChecksType) unmarshaller.unmarshal(new File("/path/to/xml"));

    Marshaller marshaller = jc.createMarshaller();
    marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true);
    marshaller.marshal(chksType, System.out);

    System.err.println(chksType.getCheck().get(0).getKey());

    for (CheckType checkType : chksType.getCheck()) {
        System.out.println("key = " + checkType.getKey() + ", " + checkType);
    }

and here is my JAXB generated classes .. ChecksType (Root element)

   @XmlAccessorType(XmlAccessType.FIELD)
   @XmlType(name = "checksType", propOrder = { "check" })
   @XmlRootElement(name = "checks")
   public class ChecksType {

         @XmlElement(required = true)
         protected List<CheckType> check;


        public List<CheckType> getCheck() {
           if (check == null) {
             check = new ArrayList<CheckType>();
           }
           return this.check;
        }

   }

And checkType (the child)

    @XmlAccessorType(XmlAccessType.FIELD)
    @XmlType(name = "checkType")
    public class CheckType {

         @XmlAttribute(name = "key")
         protected String key;


         public String getKey() {
            return key;
         }


         public void setKey(String value) {
            this.key = value;
         }

   }

Upvotes: 0

Related Questions