December 21, 2010

Represent String Values as Element Names with JAXB ("foo" as <foo/>)

In this example we will map a String value to an element name.  Instead of <foo>bar</foo> we want <bar/>.  We will accomplish this by using @XmlAdapter and @XmlElementRef.


Java Model

The following Java model will be used for this example:


package blog.string2element;

public class Customer {

    private String name;
    private String status;

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getStatus() {
        return status;
    }

    public void setStatus(String status) {
        this.status = status;
    }

}

XML (input.xml)

The name and status properties are both Strings, but the XML representations are quite different.  The name property will map to the text value of an element, but the status property will map to the name of an element.

<customer>
    <name>Jane Doe</name>
    <GOLD/>
</customer>

XML Adapter

In order to have the value represented as an empty element we will need to have a Class corresponding to each possible String value.  Then the XmlAdapter will convert between the String and Object values.  We will make use of @XmlElementRef on the Customer's status property so each Status class will need an @XmlRootElement annotation.

package blog.string2element;

import javax.xml.bind.annotation.XmlRootElement;
import javax.xml.bind.annotation.XmlSeeAlso;
import javax.xml.bind.annotation.adapters.XmlAdapter;

public class StatusAdapter extends XmlAdapter<StatusAdapter.Status, String> {

    private static final String GOLD = "GOLD";
    private static final String SILVER = "SILVER";
    private static final String BRONZE = "BRONZE";

    @Override
    public String unmarshal(Status v) throws Exception {
        if(Gold.class == v.getClass()) {
            return GOLD;
        } else if(Silver.class == v.getClass()) {
            return SILVER;
        } else if(Bronze.class == v.getClass()) {
            return BRONZE;
        }
        return null;
    }

    @Override
    public Status marshal(String v) throws Exception {
        if(GOLD.equals(v)) {
            return new Gold();
        } else if(SILVER.equals(v)) {
            return new Silver();
        } else if(BRONZE.equals(v)) {
            return new Bronze();
        }
        return null;
    }

    @XmlSeeAlso({Gold.class, Silver.class, Bronze.class})
    public static abstract class Status {
    }

    @XmlRootElement(name=GOLD)
    public static class Gold extends Status {
    }

    @XmlRootElement(name=SILVER)
    public static class Silver extends Status {
    }

    @XmlRootElement(name=BRONZE)
    public static class Bronze extends Status {
    }

}

Annotated Java Model

In our Java model we will annotate the status property with both @XmlElementRef and @XmlAdapter.  This will cause the property value to be derived from the @XmlRootElement values of the adapted classes.

package blog.string2element;

import javax.xml.bind.annotation.XmlElementRef;
import javax.xml.bind.annotation.XmlRootElement;
import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter;

@XmlRootElement
public class Customer {

    private String name;
    private String status;

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    @XmlElementRef
    @XmlJavaTypeAdapter(StatusAdapter.class)
    public String getStatus() {
        return status;
    }

    public void setStatus(String status) {
        this.status = status;
    }

} 
 
Demo Code

The following code can be used to demonstrate the solution.

package blog.string2element;

import java.io.File;
import javax.xml.bind.JAXBContext;
import javax.xml.bind.Marshaller;
import javax.xml.bind.Unmarshaller;

public class Demo {

    public static void main(String[] args) throws Exception {
        JAXBContext jc = JAXBContext.newInstance(Customer.class);

        Unmarshaller unmarshaller = jc.createUnmarshaller();
        File xml = new File("input.xml");
        Customer customer = (Customer) unmarshaller.unmarshal(xml);

        Marshaller marshaller = jc.createMarshaller();
        marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true);
        marshaller.marshal(customer, System.out);
    }

} 
 
Further Reading

If you enjoyed this post, then you may also like:

2 comments:

  1. how to add annotation to the status property in auto generated classes. We are generating the classes using xjc command.

    ReplyDelete

Note: Only a member of this blog may post a comment.