user1849859 user1849859 - 1 month ago 19
Java Question

How can I make a class field become a tag name using JAXB?

I am using Java and JAXB for XML processing.

I have the following class:

public class Characteristic {

private String characteristic;
private String value;

@XmlAttribute
public String getCharacteristic() {
return characteristic;
}

public void setCharacteristic(String characteristic) {
this.characteristic = characteristic;
}

@XmlValue
public String getValue() {
return value;
}

public void setValue(String value) {
this.value = value;
}
}

public static void main(String[] args) {
Characteristic c = new Characteristic();
c.setCharacteristic("store_capacity");
c.setValue(40);
Characteristic c2 = new Characteristic();
c2.setCharacteristic("number_of_doors");
c2.setValue(4);
}


This is the result that I get:

<characteristics characteristic="store_capacity">40</characteristics>
<characteristics characteristic="number_of_doors">4</characteristics>


I want to get the following result:

<store_capacity>40</store_capacity>
<number_of_doors>4</number_of_doors>


How can I achieve this?

Answer

You can use a combination of @XmlElementRef and JAXBElement to produce dynamic element names.

The idea is:

  • Make Characteristic a subclass of JAXBElement and override the getName() method to return the name based on the characteristic property.
  • Annotate characteristics with @XmlElementRef.
  • Provide the @XmlRegistry (ObjectFactory) with an @XmlElementDecl(name = "characteristic").

Below is a working test.

The test itself (nothing special):

@Test
public void marshallsDynamicElementName() throws JAXBException {
    JAXBContext context = JAXBContext.newInstance(ObjectFactory.class);
    final Characteristics characteristics = new Characteristics();
    final Characteristic characteristic = new Characteristic(
            "store_capacity", "40");
    characteristics.getCharacteristics().add(characteristic);
    context.createMarshaller().marshal(characteristics, System.out);
}

Produces:

<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<characteristics><store_capacity>40</store_capacity></characteristics>

Let's start with the characteristics root element class. It has a characteristics property which is annotated with @XmlElementRef. This means that the contents should be either JAXBElements or @XmlRootElement-annotated class instances.

@XmlRootElement(name = "characteristics")
public class Characteristics {

    private final List<Characteristic> characteristics = new LinkedList<Characteristic>();

    @XmlElementRef(name = "characteristic")
    public List<Characteristic> getCharacteristics() {
        return characteristics;
    }

}

In order for this to work you also need an ObjectFactory or something annotated with @XmlRegistry having a corresponding @XmlElementDecl:

@XmlRegistry
public class ObjectFactory {

    @XmlElementDecl(name = "characteristic")
    public JAXBElement<String> createCharacteristic(String value) {
        return new Characteristic(value);
    }

}

Recall, the characteristics property must contain either @XmlRootElement-annotated class instances or JAXBElements. @XmlRootElement is not suitable since it's static. But JAXBElement is dynamic. You can subclass JAXBElement and override the getName() method:

public class Characteristic extends JAXBElement<String> {
    private static final long serialVersionUID = 1L;
    public static final QName NAME = new QName("characteristic");

    public Characteristic(String value) {
        super(NAME, String.class, value);
    }

    public Characteristic(String characteristic, String value) {
        super(NAME, String.class, value);
        this.characteristic = characteristic;
    }

    @Override
    public QName getName() {
        final String characteristic = getCharacteristic();
        if (characteristic != null) {
            return new QName(characteristic);
        }
        return super.getName();
    }

    private String characteristic;

    @XmlTransient
    public String getCharacteristic() {
        return characteristic;
    }

    public void setCharacteristic(String characteristic) {
        this.characteristic = characteristic;
    }
}

In this case I've overridden the getName() method to dynamically determine the element name. If characteristic property is set, its value will be used as the name, otherwise the method opts to the default characteristic element.

The code of the test is available on GitHub.