2

I have an XML to unmarshall:

<?xml version="1.0" encoding="UTF-8"?>
<ROW id='1'>
   <MOBILE>9831138683</MOBILE>
   <A>1</A>
   <B>2</B>
</ROW>

I want to map it to a class:

import java.util.*;
import javax.xml.bind.annotation.*;
import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter;
import org.eclipse.persistence.oxm.annotations.XmlPath;

@XmlRootElement
@XmlAccessorType(XmlAccessType.FIELD)
public class ROW {

    @XmlPath(".")
    @XmlJavaTypeAdapter(MapAdapter.class)
    private Map<String, Integer> map = new HashMap<String, Integer>();

    @XmlAttribute
    private int id;
    @XmlElement(name = "MOBILE")
    private int mobileNo;

}

For this I tried the bdoughan blog where it uses @XmlVariableNode("key") :

MapAdapter:

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;

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

import org.eclipse.persistence.oxm.annotations.XmlVariableNode;

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

    public static class AdaptedMap {

        @XmlVariableNode("key")
        List<AdaptedEntry> entries = new ArrayList<AdaptedEntry>();

    }

    public static class AdaptedEntry {

        @XmlTransient
        public String key;

        @XmlValue
        public String value;

    }

    @Override
    public AdaptedMap marshal(Map<String, String> map) throws Exception {
        AdaptedMap adaptedMap = new AdaptedMap();
        for(Entry<String, String> entry : map.entrySet()) {
            AdaptedEntry adaptedEntry = new AdaptedEntry();
            adaptedEntry.key = entry.getKey();
            adaptedEntry.value = entry.getValue();
            adaptedMap.entries.add(adaptedEntry);
        }
        return adaptedMap;
    }

    @Override
    public Map<String, String> unmarshal(AdaptedMap adaptedMap) throws Exception {
        List<AdaptedEntry> adaptedEntries = adaptedMap.entries;
        Map<String, String> map = new HashMap<String, String>(adaptedEntries.size());
        for(AdaptedEntry adaptedEntry : adaptedEntries) {
            map.put(adaptedEntry.key, adaptedEntry.value);
        }
        return map;
    }

}

Using this approach all the keys(MOBILE, id, A, B) are mapped inside the Map. I want to unmarshall such that all defined attributes the id, MOBILE are mapped to their attributes in POJO and rest all are mapped to Map.

How can this be achieved ?

Siddharth Trikha
  • 2,648
  • 8
  • 57
  • 101

1 Answers1

1

I have a solution for you, but slightly different than what you try above.

Let's take the root class:

@XmlAccessorType(XmlAccessType.FIELD)
@XmlRootElement(name = "ROW")
public class Row {

    @XmlAttribute
    private int id;
    @XmlElement(name = "MOBILE")
    private int mobileNo;

    @XmlMixed
    @XmlAnyElement
    @XmlJavaTypeAdapter(MyMapAdapter.class)
    private Map<String, String> otherElements;
}

And the adapter for turning the uknown values into a map:

import org.w3c.dom.Document;
import org.w3c.dom.Element;

import javax.xml.bind.annotation.adapters.XmlAdapter;
import javax.xml.parsers.DocumentBuilderFactory;
import java.util.HashMap;
import java.util.Map;

public class MyMapAdapter extends XmlAdapter<Element, Map<String, String>> {

    private Map<String, String> hashMap = new HashMap<>();

    @Override
    public Element marshal(Map<String, String> map) throws Exception {
        // expensive, but keeps the example simpler
        Document document = DocumentBuilderFactory.newInstance().newDocumentBuilder().newDocument();

        Element root = document.createElement("dynamic-elements");

        for(Map.Entry<String, String> entry : map.entrySet()) {
            Element element = document.createElement(entry.getKey());
            element.setTextContent(entry.getValue());
            root.appendChild(element);

        }

        return root;
    }


    @Override
    public Map<String, String> unmarshal(Element element) {
        String tagName = element.getTagName();
        String elementValue = element.getChildNodes().item(0).getNodeValue();
        hashMap.put(tagName, elementValue);

        return hashMap;
    }
}

This will put id and mobile number in the fields, and the rest, the uknown into a map.

The marshalling will not be exactly as the xml you showed. It puts a wrapper around the dynamic values and looks like this:

<ROW id="1">
    <MOBILE>1241204091</MOBILE>
    <dynamic-elements>
        <A>1</A>
        <B>2</B>
    </dynamic-elements>
</ROW>
martidis
  • 2,897
  • 1
  • 11
  • 13
  • I tried the above example for XML string in the question, the unmarshalling works as per expected; I had a couple of questions: A) Shall I be worried about `element.getChildNodes().item(0).getNodeValue()` in unmarshall method, I see a potential NPE issues ? B) How to make marshall method work ? It would be helpful if you update the marshall method (I am a novice with this). – Siddharth Trikha Oct 24 '19 at 09:34
  • 1
    @SiddharthTrikha for A, you can do what checks you need to not end up with issues. For B, it is trickier than I expected. Trying to figure it out. If/When I manage, I will update answer – martidis Oct 24 '19 at 11:29
  • Okay. I guess for marshall method we would have a `List` of `Element` required ? – Siddharth Trikha Oct 30 '19 at 04:31
  • @SiddharthTrikha I still haven't thought of a proper way to do this, but in my attempts I changed the adapter to a more flexible approach. I use object instead of element. I will update in the answer, may it helps you and later today will give another try. – martidis Oct 30 '19 at 05:52
  • I tried the marshal method with `AdaptedMap` approach as there in the question, it didn't really work : `{ "ROW" : { "rn" : "deviceLight", "value" : "package.MyMapAdapterSO$AdaptedMap@5b218417" } }` – Siddharth Trikha Oct 31 '19 at 05:10
  • @SiddharthTrikha this case is tricky (using my approach). It works find for unmarshalling but for marshalling I couldn't make it give you the exact same xml. I can do it with a wrapper around the dynamic values. I will update the answer and hope it is "good enough" for your context – martidis Oct 31 '19 at 08:57
  • Well, I wouldn't want a dynamic element in XML while marshalling. Won't an `XmlAnyElement` annotation work for marshalling ? – Siddharth Trikha Nov 01 '19 at 08:35
  • One more thing what if I want this unmarshalling/marshalling for a string with any root element i.e root element can be anything not fixed to "ROW" as in this example ?? – Siddharth Trikha Nov 01 '19 at 08:48
  • @SiddharthTrikha I think you cannot have dynamic root element, at least using jaxb – martidis Nov 01 '19 at 08:50
  • Maybe I could just ignore the root element in such cases ? I have multiple use cases where format is same as given in the example just that they will come with different `prefix:rootName`. IS that possible ? – Siddharth Trikha Nov 01 '19 at 08:52
  • @SiddharthTrikha You cant ignore the root element, cant do marshal/unmarshal without it – martidis Nov 01 '19 at 08:54
  • Let us [continue this discussion in chat](https://chat.stackoverflow.com/rooms/201709/discussion-between-siddharth-trikha-and-mart). – Siddharth Trikha Nov 01 '19 at 08:55
  • Putting a wrapper `dynamic-elements` in marshaling is not feasible as per my requirement. Another approach to look at this ? – Siddharth Trikha Nov 27 '19 at 04:22
  • @SiddharthTrikha I couldn't find a better solution for marshaling without the wrapper – martidis Nov 27 '19 at 13:14
  • Hi, I am trying this approach for my `unmarshalling` where I need to store the `userExtensions` elements coming from the `XML` within a `Map `. These informations are provided by user so it can be random. I tried this approach but for some reason the `unmarshalling` method within my `MapAdapter` class is not being called at all. I have posted the question here, if you get a chance can you please have a look and provide your suggestions? https://stackoverflow.com/questions/67631172/jaxb-xmlanyelement-with-xmladapter-does-not-make-a-call-to-the-unmarshal-method – BATMAN_2008 May 22 '21 at 10:24
  • @BATMAN_2008 sorry for the late response, went to see the link and saw u figured it out already (didn't check what you did tho). Happy to see its resolved – martidis May 26 '21 at 11:22
  • @martidis Thanks for your response. I was able to solve it after trying a lot of things. Now I am struck in one more issue when I solved that. It would be really great if you can provide some suggestion for this issue: https://stackoverflow.com/questions/67648941/jaxb-moxy-unmarshalling-assigns-all-field-values-to-mapstring-object-rather-th –  May 26 '21 at 11:52
  • 1
    @BATMAN_2008 no worries. will have a look and if i figure it out I will answer on the issue – martidis May 26 '21 at 14:56