JAXB element name based on object property

I need to create an object model for the following XML:

XML Example 1:

<InvoiceAdd> <TxnDate>2009-01-21</TxnDate> <RefNumber>1</RefNumber> <InvoiceLineAdd> </InvoiceLineAdd> </InvoiceAdd> 

XML example 2:

 <SalesOrderAdd> <TxnDate>2009-01-21</TxnDate> <RefNumber>1</RefNumber> <SalesOrderLineAdd> </SalesOrderLineAdd> </SalesOrderAdd> 

XML output will be based on a single string parameter or enumeration. String txnType = "Invoice"; (or "SalesOrder");

I would use one TransactionAdd class:

 @XmlRootElement public class TransactionAdd { public String txnDate; public String refNumber; private String txnType; ... public List<LineAdd> lines; } 

instead of using subclasses or anything else. The code that creates the TransactionAdd instance is the same for both types of transactions; it differs only from the type.

This XML is used by the fairly well-known QuickBooks product and is used by the QuickBooks web service, so I cannot change the XML, but I want to simplify setting the element name based on the property (txnType).

I would consider something like a method to determine the name of a target element:

 @XmlRootElement public class TransactionAdd { public String txnDate; public String refNumber; private String txnType; ... public List<LineAdd> lines; public String getElementName() { return txnType + "Add"; } } 

Various transactions will be created using the following code:

 t = new TransactionAdd(); t.txnDate = "2010-12-15"; t.refNumber = "123"; t.txnType = "Invoice"; 

The goal is to serialize the t object with the name of the top-level element based on txnType. For example:.

 <InvoiceAdd> <TxnDate>2009-01-21</TxnDate> <RefNumber>1</RefNumber> </InvoiceAdd> 

In the case of t.txnType = "SalesOrder", the result should be

 <SalesOrderAdd> <TxnDate>2009-01-21</TxnDate> <RefNumber>1</RefNumber> </SalesOrderAdd> 

Currently, I see only one workaround with subclasses of InvoiceAdd and SalesOrderAdd and using the @XmlElementRef annotation to have a name based on the class name. But he will need to instantiate different classes based on the type of transaction, and also have two other classes, InvoiceLineAdd and SalesOrderLineAdd, which look pretty ugly.

Please offer me some solution for this. I would think of something simple.

+7
source share
2 answers

To solve the aspect of the root element that you could do, you would need to use @XmlRegistry and @XmlElementDecl. This will give us some possible root elements for the TransactionAdd class:

 import javax.xml.bind.JAXBElement; import javax.xml.bind.annotation.XmlElementDecl; import javax.xml.bind.annotation.XmlRegistry; import javax.xml.namespace.QName; @XmlRegistry public class ObjectFactory { @XmlElementDecl(name="InvoiceAdd") JAXBElement<TransactionAdd> createInvoiceAdd(TransactionAdd invoiceAdd) { return new JAXBElement<TransactionAdd>(new QName("InvoiceAdd"), TransactionAdd.class, invoiceAdd); } @XmlElementDecl(name="SalesOrderAdd") JAXBElement<TransactionAdd> createSalesOrderAdd(TransactionAdd salesOrderAdd) { return new JAXBElement<TransactionAdd>(new QName("SalesOrderAdd"), TransactionAdd.class, salesOrderAdd); } } 

The TransactionAdd class will look like this. It is interesting to note that we will create the txnType @XmlTransient property.

 import java.util.List; import javax.xml.bind.annotation.XmlElement; import javax.xml.bind.annotation.XmlTransient; public class TransactionAdd { private String txnDate; private String refNumber; private String txnType; private List<LineAdd> lines; @XmlElement(name="TxnDate") public String getTxnDate() { return txnDate; } public void setTxnDate(String txnDate) { this.txnDate = txnDate; } @XmlElement(name="RefNumber") public String getRefNumber() { return refNumber; } public void setRefNumber(String refNumber) { this.refNumber = refNumber; } @XmlTransient public String getTxnType() { return txnType; } public void setTxnType(String txnType) { this.txnType = txnType; } public List<LineAdd> getLines() { return lines; } public void setLines(List<LineAdd> lines) { this.lines = lines; } } 

Then we need to put a little logic outside the JAXB operation. For non-marshals, we will use the local part of the root element name to populate the txnType property. For the marshal, we will use the value of the txnType property to create the corresponding JAXBElement.

 import java.io.File; import javax.xml.bind.JAXBContext; import javax.xml.bind.JAXBElement; 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(TransactionAdd.class, ObjectFactory.class); File xml = new File("src/forum107/input1.xml"); Unmarshaller unmarshaller = jc.createUnmarshaller(); JAXBElement<TransactionAdd> je = (JAXBElement<TransactionAdd>) unmarshaller.unmarshal(xml); TransactionAdd ta = je.getValue(); ta.setTxnType(je.getName().getLocalPart()); JAXBElement<TransactionAdd> jeOut; if("InvoiceAdd".equals(ta.getTxnType())) { jeOut = new ObjectFactory().createInvoiceAdd(ta); } else { jeOut = new ObjectFactory().createSalesOrderAdd(ta); } Marshaller marshaller = jc.createMarshaller(); marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true); marshaller.marshal(jeOut, System.out); } } 

Make

Now I will consider using the lines property.

+3
source

You can use the XmlAdapter for this. Based on the String value of the txnType property, you should have the Marshal XmlAdapter of the instance of the object matching InvoiceLineAdd or SalesOrderLineAdd.

Here's what it would look like:

Transactionactiond

In the txnType property, we will use a combination of @XmlJavaTypeAdapter and @XmlElementRef:

 import javax.xml.bind.annotation.XmlElementRef; import javax.xml.bind.annotation.XmlRootElement; import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter; @XmlRootElement public class TransactionAdd { private String txnType; @XmlJavaTypeAdapter(MyAdapter.class) @XmlElementRef public String getTxnType() { return txnType; } public void setTxnType(String txnType) { this.txnType = txnType; } } 

Adapted objects will look like this:

AbstractAdd

 import javax.xml.bind.annotation.XmlSeeAlso; @XmlSeeAlso({InvoiceAdd.class, SalesOrderAdd.class}) public class AbstractAdd { } 

Invoiceadd

 import javax.xml.bind.annotation.XmlRootElement; @XmlRootElement public class InvoiceAdd extends AbstractAdd { } 

SalesOrderAdd

 import javax.xml.bind.annotation.XmlRootElement; @XmlRootElement public class SalesOrderAdd extends AbstractAdd { } 

The XmlAdapter for converting between string and adapted objects will look like this:

 import javax.xml.bind.annotation.adapters.XmlAdapter; public class MyAdapter extends XmlAdapter<AbstractAdd, String> { @Override public String unmarshal(AbstractAdd v) throws Exception { if(v instanceof SalesOrderAdd) { return "salesOrderAdd"; } return "invoiceAdd"; } @Override public AbstractAdd marshal(String v) throws Exception { if("salesOrderAdd".equals(v)) { return new SalesOrderAdd(); } return new InvoiceAdd(); } } 

You can use the following demo code:

 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(TransactionAdd.class); File xml = new File("input.xml"); Unmarshaller unmarshaller = jc.createUnmarshaller(); TransactionAdd ta = (TransactionAdd) unmarshaller.unmarshal(xml); Marshaller marshaller = jc.createMarshaller(); marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true); marshaller.marshal(ta, System.out); } } 

To create / use the following XML:

 <transactionAdd> <salesOrderAdd/> </transactionAdd> 

For more information see

+3
source

All Articles