0

I need to change List to Map in JAXB unmarshalling file.

The xml file

<jrx:person>
    <jrx:ulement name="id" type="Integer" value="1"/>
    <jrx:ulement name="name" type="String" value="neps"/>
    </jrx:person>

The java classes,

@XmlRootElement(name = "person")
@XmlAccessorType(XmlAccessType.FIELD)
public class Person {

    @XmlElement(name = "ulement")
    private List<Property> props;
}

@XmlRootElement(name = "ulement")
@XmlAccessorType(XmlAccessType.FIELD)
public class Property {

    @XmlAttribute
    protected String name;

    @XmlAttribute
    private String type;

    @XmlAttribute
    private String value;
}

I have to change the class implementation to

@XmlRootElement(name = "person")
@XmlAccessorType(XmlAccessType.FIELD)
public class Person {

    @XmlElement(name = "ulement")
    **private Map<String, Property> props;**
}

So that i ll be able to fetch the property using the keys quickly. Please suggest me some implementation to make it work.


I have done the following changes but still the values are not mapped,

The classes,

@XmlRootElement(name = "person")
@XmlAccessorType(XmlAccessType.FIELD)
public class Person {

    @XmlJavaTypeAdapter(value = PropertyAdapter.class)
    private Map<String, Property> map;

}

public class PropertyAdapter extends XmlAdapter<Properties, Map<String, Property>> {

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

    @Override
    public Map<String, Property> unmarshal(Properties p) throws Exception {
        Map<String, Property> map = new HashMap<String, Property>();
        for (Property entry : p.props) { 
            map.put(entry.name, entry);
        }
        return map;
    }

}

public class Properties {

    @XmlElement(name = "ulement")
    public List<Property> props;
}

But the values r Null at ,

@XmlRootElement(name = "person")
@XmlAccessorType(XmlAccessType.FIELD)
public class Person {

    **@XmlJavaTypeAdapter(value = PropertyAdapter.class)
    private Map<String, Property> map;**

}

What exactly is missing over there.

Andy
  • 49,085
  • 60
  • 166
  • 233
user3095047
  • 131
  • 2
  • 9

1 Answers1

0

Usually, you would use @XmlJavaTypeAdapter and the XmlAdapter superclass on Person to transform your List into a Map and the opposite.

The problem is that XmlJavaTypeAdapter does not apply to XmlRootElement.

I see two solution to your problem :

1. Executing the adapter logic before marshalling and after unmarshalling.

@XmlRootElement(name = "person",namespace="yourNamespace")
@XmlAccessorType(XmlAccessType.FIELD)
public class Person {
    @XmlElement(name ="ulement",namespace="yourNamespace")
    private List<Property> listProperty;

    public Person(AdaptedPerson p){
        this.listProperty = p.getMap().values();
    }

    //getters and setters
}


public class AdaptedPerson {

    private Map<String,Property> map;

    public AdaptedPerson(Person person){
        map = new HashMap<>();
        for(Property p : person.getListProperty()){
            map.put(p.getName(),p);
        } 
    }

    //getters and setters

}

Then each time you unmarshall a person :

AdaptedPerson ap = new AdaptedPerson(p);

And before you marshall an AdaptedPerson :

Person p = new Person(ap);

2. Implementing the map logic directly inside the Person class

@XmlRootElement(name = "person",namespace="yourNamespace")
@XmlAccessorType(XmlAccessType.FIELD)
public class Person {

    @XmlTransient
    private Map<String,Property> map;

    @XmlElement(name ="ulement",namespace="yourNamespace")
    private List<Property> properties;

    public Property getProperty(String name){
        if(map == null) this.init();
        return map.get(name);
    }

    public Property addProperty(Property p){
        if(map == null) this.init();
        map.put(p.getName(),p);
        properties.remove(p);
        properties.add(p);
    }

    public Boolean removeProperty(Property p){
        if(map == null) this.init();
        map.remove(p.getName());
        return properties.remove(p);
    }

    //More methods if you need them.

    private void init(){
        if(properties == null) properties = new ArrayList<>();
        map = new HashMap<>();
        for(Property p : properties){
            map.put(p.getName(),p);
        } 
    }


}

The problem with this method is that for each method of Map you want to use, you will have to code a method that keeps the list updated.

Dimpre Jean-Sébastien
  • 1,067
  • 1
  • 6
  • 14
  • This change is working public class PropertyAdapter extends XmlAdapter> { public Map unmarshal(Property p) throws Exception { Map map = new HashMap(); map.put(p.getName(), p); return map; } } – user3095047 Aug 24 '16 at 11:40
  • The PropertyAdapter called for every element in the list, therefore always the last element of the list is present in the map. How do we refer to this.map of the Person class itself instead of creating new map every time ? – user3095047 Aug 24 '16 at 11:46
  • If the XmlAdapter , Map>, then getting the following error : - com.sun.xml.bind.v2.runtime.IllegalAnnotationsException: 1 counts of IllegalAnnotationExceptions java.util.List is an interface, and JAXB can't handle interfaces. – user3095047 Aug 24 '16 at 12:53
  • I still want to marshall and unmarshall with the List, what will be the correct approach ? – user3095047 Aug 24 '16 at 14:39
  • Should have seen the exception coming ..., is the jrx:person element is the root element or it is part of a bigger XML file ? If it's not the root element, you can use an XmlJavaTypeAdapter on Person instead of the map (Tell me if you need help in that case). If it is, the most simple solution is to apply the Adapter logic after unmarshalling and before marshalling, since XmlJavaTypeAdapter does not work on RootElement. – Dimpre Jean-Sébastien Aug 24 '16 at 15:16
  • The is the root element by itself which contains the list of . If applying XmlJavaTypeAdapter on Person will give Person class to map the ulements, then i will try both the approaches. Also how to apply the Adapter logic after unmarshalling and before marshalling ? – user3095047 Aug 25 '16 at 06:10
  • Updated answer. Basically, you cannot use XmlAdapter on RootElement so you have to manually manage either the adaptation or the map manually. – Dimpre Jean-Sébastien Aug 25 '16 at 08:30
  • I look for the similar kind of work around only. I want to make it marshalled and unmarshalled to the root object without using any extra dependencies like EclipseLink Moxy other than JAXB, If anything how can explicitly call the adapter logic before doing a marshal or after doing an unmarshal from the root object ? – user3095047 Aug 25 '16 at 09:17
  • This works , @XmlRootElement(name = "person") @XmlAccessorType(XmlAccessType.FIELD) public class Person { @XmlElement(name = "ulement") public List props; }, but i require to map it this way, @XmlRootElement(name = "person") @XmlAccessorType(XmlAccessType.FIELD) public class Person { @XmlElement(name = "ulement") public Map props; } to the root element object only. If we could override the xmlelement mapping to specific variable then how can override before/after marshalling or unmarshalling of specific xmlelement ? – user3095047 Aug 25 '16 at 09:22
  • I'm afraid what you want is not possible. See http://stackoverflow.com/questions/21640566/using-an-adapter-to-marshal-a-class-to-a-root-element-with-moxy-or-any-other-jax . Also see http://stackoverflow.com/questions/11966714/xmljavatypeadapter-not-being-detected for the workaround, which is pretty similar to what i've made in my answer – Dimpre Jean-Sébastien Aug 25 '16 at 11:51