0

I have a XML file like the following:

<root>
  <item>
    <id>my id</id>
    <type>my type</id>
    <key1>value1</key1>
    <key2>value1</key2>
    <key3>value1</key3>
  </item>

  <item>
    <id>my id</id>
    <type>my type</type>
    <other-key1>value1</other-key1>
    <other-key2>value1</other-key2>
  </item>

  (...)

</root>

And I want to parse the XML on a class like this:

public class Item {
    private String id;

    private String type;

    private Map<String, String> values;
}

Where the id node and the type node are mapped to the attributes with the same name and everyting else inside a item are added to a map.

I have tried write a class with the annotations XmlJavaTypeAdapter and XmlAnyElement but this does not work how I expect:

(...)

public class Item {
    @XmlElement
    private String id;

    @XmlElement
    private String type;

    @XmlAnyElement
    @XmlJavaTypeAdapter(MapAdapter.class)
    private Map<String, String> values;
}


public class MapAdapter extends XmlAdapter<Object, Map<String, String>> {
    @Override
    public Object marshal(Map<String, String> v) throws Exception {
    }

    @Override
    public Map<String, String> unmarshal(Object v) throws Exception {
        // v is a node instead an array of nodes.
        // I want to get an array of nodes to convert them to a map.
        //
        // eg:
        //  
        //   key1 -> value1
        //   key2 -> value2
        //   key3 -> value3
        //
    }

Any idea how I can get this?

UPDATE

A clarification of my question:

My problem is that the elements that I want to unmarshal within the map are not inside a wrapper and they are mixed with another elements at the same level.

I tried to use the XmlAnyElement and XmlJavaTypeAdapter to get a map of all the remaining elements. However there is an implicit order on these annotations and what I get is a collection of elements parsed with the adapter:

XmlAnyElement finds each remaining element, parse it with the adapter and add the result to a collection.

Is there any way to reverse this?

I want to get a collection of all the remaining elements and then parse the collection with the XmlAdapter to get a map.

Garet
  • 365
  • 2
  • 13
  • See this one: [JAXB: how to marshall map into value](https://stackoverflow.com/questions/3941479/jaxb-how-to-marshall-map-into-keyvalue-key?rq=1) – Jesper Aug 09 '17 at 13:34
  • That does not work for me. I need to parse a List of Element into a Map and the nodes with the elements are not inside a tag : value1value2 – Garet Aug 10 '17 at 10:23
  • May be it's just a typo: But in your `Item` class you need to annotate `id` and `type` with `@XmlElement`, not with `@XmlAttribute`. Otherwise their XML would also end up in your `@XmlAnyElement` during unmarshalling. – Thomas Fritsch Aug 14 '17 at 17:08
  • It was a typo error. Changed, thanks! – Garet Aug 15 '17 at 07:31

1 Answers1

2

Like you I didn't succeed in writing an XmlAdapter. But there is another possible approach:

In your Item class declare a List<Element> annotated with @XmlAnyElement, so that JAXB will use it for marshalling/unmarshalling.

But you want a Map<String, String>. Hence you declare that too, but annotated with @XmlTransient, so that JAXB will not use it for marshalling/unmarshalling.

Finally implement a private method afterUnmarshal(Unmarshaller unmarshaller, Object parent) where you shovel contents from the List<Element> to the Map<String, String>. As described in Unmarshal Event Callbacks JAXB will call this method at appropriate times.

If you need to write XML files you may also need a private method beforeMmarshal(Marshaller marshaller) where you shovel contents from the Map<String, String> back to the List<Element>. As described in Marshal Event Callbacks JAXB will call this method at appropriate times.

public class Item {

    @XmlElement
    private String id;

    @XmlElement
    private String type;

    @XmlAnyElement
    private List<Element> otherElements;

    @XmlTransient  // don't participate in JAXB marshalling/unmarshalling
    private Map<String, String> values;

    @SuppressWarnings("unused")   // called only by JAXB
    private void afterUnmarshal(Unmarshaller unmarshaller, Object parent) {
        values = new HashMap<>();
        if (otherElements != null) {
            for (Element element : otherElements) {
                String key = element.getNodeName();
                String value = element.getFirstChild().getNodeValue();
                values.put(key, value);
            }
        }
    }

}
Thomas Fritsch
  • 9,639
  • 33
  • 37
  • 49