0

Currently I have following output from my program:

<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<container>
    <elements>
        <property name="e1">
            <foo name="Alex" status="Married"/>
        </property>
        <property name="e2">
            <foo name="Chris" status="Married with 2 children"/>
        </property>
    </elements>
</container>

As you can see, having both <container> and <elements> tags is useless. I'd like to remove <elements>, so the output would look like:

<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<container>
    <property name="e1">
        <foo name="Alex" status="Married"/>
    </property>
    <property name="e2">
        <foo name="Chris" status="Married with 2 children"/>
    </property>
</container>

Code that's generating first output is listed below:

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

    @XmlElement
    @XmlJavaTypeAdapter(MyAdapter.class)
    private Map<String, Foo> elements = new HashMap<String, Foo>();

    public Container() {
        this.elements = new HashMap<String, Foo>();
    }

    public Map<String, Foo> getElements() {
        return elements;
    }

    @XmlAccessorOrder(XmlAccessOrder.ALPHABETICAL)
    @XmlRootElement(name = "foo")
    static class Foo {
        @XmlAttribute
        public String name;

        @XmlAttribute
        public String status;

        public Foo(String name, String status) {
            this.name = name;
            this.status = status;
        }

        public Foo() {
        }
    }

    public static void main(String[] args) throws JAXBException {
        final JAXBContext context = JAXBContext.newInstance(Container.class);
        final Marshaller m = context.createMarshaller();
        m.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true);

        final Container c = new Container();
        final Map<String, Foo> elementsMap = c.getElements();
        elementsMap.put("e1", new Foo("Alex", "Married"));
        elementsMap.put("e2", new Foo("Chris", "Married with 2 children"));

        m.marshal(c, System.out);
    }
}

And MyAdapter class, based on JAXB @XmlAdapter: Map -> List adapter? (marshall only) :

public class MyAdapter extends XmlAdapter<MyAdapter.AdaptedFoo, Map<String, Foo>> {

    static class AdaptedFoo {
        public List<Property> property = new ArrayList<>();
    }

    public static class Property {
        @XmlAttribute
        public String name;

        @XmlElementRef(type = Foo.class)
        public Foo value;
    }

    @Override
    public Map<String, Foo> unmarshal(AdaptedFoo v) throws Exception {
        return null;
    }

    @Override
    public AdaptedFoo marshal(Map<String, Foo> map) throws Exception {
        if (null == map) {
            return null;
        }
        AdaptedFoo adaptedFoo = new AdaptedFoo();
        for (Entry<String, Foo> entry : map.entrySet()) {
            Property property = new Property();
            property.name = entry.getKey();
            property.value = entry.getValue();
            adaptedFoo.property.add(property);
        }
        return adaptedFoo;
    }
}

How can I remove <elements> tag from my output?


edit: I've found 'dirty' way to do it - setting @XmlRootElement(name = "") for Container class. But is there any 'elegant' way?

1
  • Instead of adapting the Map to a wrapper object that has a List<Property>, have you tried adapting it directly to the List<Property> without the intervening AdaptedFoo? Commented Aug 15, 2014 at 16:15

2 Answers 2

1

The XML element <elements> is required to associate the enclosed element seqence with the property Map<?,?> elements. You can't drop it: how would an unmarshaller know where the <property> elements belong *on the level of the element <container>.

Having a List<Property> is different since JAXB handles repeated elements x "hardwired" as a List<?> x, so there's no need for a wrapper.

Since you write Java classes with annotations, you could use this by adding (to Container) another "virtual" field and modify some annotations:

@XmlAccessorType(XmlAccessType.PROPERTY)
public class Container {
// ...
@XmlTransient    
public Map<String,Foo> getElements(){
    return elements;
}
private List<Property> property;
@XMLElement
public List<Property> getProperty(){
    List<Property>  props = elements.entrySet().stream()
                            .map( e -> new Property( e.getKey(), e.getValue() )
                            .collect( Collectors.toList() );
    return props;
}

Older Javas might do

   List<Property> props = new ArrayList<>();
   for( Map.Entry<String,Foo> e: elements.entrySet() ){
       props.add( new Property( e.getKey(), e.getValue() ) );
   }
   return props;

This marshals like this:

<container>
    <property name="aaa">
       <foo name="aaaname" status="aaastatus"/>
    </property>
    <property name="bbb">
       <foo name="bbbname" status="bbbstatus"/>
    </property>
</container>

It doesn't unmarshal!

2
  • I forgot to mention that I'm using Java 7 - .stream() is part of newer Java. Could you please rewrite the code for older Java version? Commented Aug 12, 2014 at 15:16
  • Sorry, must have missed your comment - added older code.
    – laune
    Commented Aug 15, 2014 at 15:56
0

Other than removing the tag by "hacking" the element name to be "", I don't think you're going to be able to get around using that data type without the nested tag.

The tag is describing the data type that encompasses your adapted class, which you've annotated as an XML element called elements, which contains properties defined by your adapter.

If you want something that more closely matches what I think you are after, either change the context of your root class, or maybe use a List instead of a HashMap in your container class.

2
  • Map is mapped to a List, check the adapter I wrote. What does it mean "change the context of your root class"? Commented Aug 12, 2014 at 15:19
  • I understand your map is mapped to a list implementation (which is what gives you the desired behavior inside the element tags), your issue is, the map in your root class has a root element associated with it, which is why you are getting the tag you don't want. What I mean by change the context of your root class is, figure out if Container is the appropriate name for it. Is it redundant? A Container might have many things, of which elements is one of them.
    – Ryan J
    Commented Aug 12, 2014 at 16:23

Not the answer you're looking for? Browse other questions tagged or ask your own question.