Reputation: 83
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
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
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