【问题标题】:JAXB element name based on object property基于对象属性的 JAXB 元素名称
【发布时间】:2010-12-14 16:43:19
【问题描述】:

我必须为以下 XML 创建对象模型:

XML 示例 1:

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

XML 示例 2:

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

XML 输出将基于单个字符串参数或枚举。 String txnType = "发票"; (或“销售订单”);

我会使用单个类 TransactionAdd:

@XmlRootElement
public class TransactionAdd {  
  public String txnDate;
  public String refNumber;

  private String txnType;
  ...

  public List<LineAdd> lines;
}

而不是使用子类或其他任何东西。创建 TransactionAdd 实例的代码对于两种类型的事务都是相同的,只是类型不同。

这个 XML 由一个名为 QuickBooks 的知名产品使用,并由 QuickBooks Web 服务使用 - 所以我无法更改 XML,但我希望能够轻松地根据属性设置元素名称(txnType )。

我会考虑类似确定目标元素名称的方法:

@XmlRootElement
public class TransactionAdd {  
  public String txnDate;
  public String refNumber;

  private String txnType;
  ...

  public List<LineAdd> lines;

  public String getElementName() {
     return txnType + "Add";
  }
}

将使用以下代码创建不同的交易:

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

目标是根据 txnType 序列化具有顶级元素名称的 t 对象。例如:

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

如果是 t.txnType = "SalesOrder",结果应该是

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

目前,我只看到一种解决方法,使用子类 InvoiceAdd 和 SalesOrderAdd 并使用 @XmlElementRef 注释来根据类名命名。但它需要根据交易类型实例化不同的类,并且还需要另外两个不同的类 InvoiceLineAdd 和 SalesOrderLineAdd,它们看起来相当丑陋。

请建议我任何解决方案来处理这个问题。我会考虑一些简单的事情。

【问题讨论】:

    标签: java jaxb quickbooks


    【解决方案1】:

    要解决根元素方面的问题,您需要利用@XmlRegistry 和@XmlElementDecl。这将为 TransactionAdd 类提供多个可能的根元素:

    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);
        }
    
    }
    

    您的 TransactionAdd 类将如下所示。值得注意的是,我们将 txnType 属性设为@XmlTransient。

    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;
        }
    
    }
    

    然后我们需要在 JAXB 操作之外提供一些逻辑。对于 unmarshal,我们将使用根元素名称的本地部分来填充 txnType 属性。对于 marshal,我们将使用 txnType 属性的值来创建适当的 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);
        }
    
    }
    

    待办事项

    接下来我将研究如何处理lines 属性。

    【讨论】:

    • 非常感谢!看起来它现在做到了。我将检查我的项目并返回我的结果!
    【解决方案2】:

    您可以为此使用 XmlAdapter。根据 txnType 属性的 String 值,您将让 XmlAdapter 编组一个对应于 InvoiceLineAdd 或 SalesOrderLineAdd 的对象实例。

    这就是它的样子:

    事务添加

    在 txnType 属性上,我们将使用 @XmlJavaTypeAdapter 和 @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;
        }
    
    }
    

    调整后的对象将如下所示:

    AbstractAdd

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

    发票添加

    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 {
    
    }
    

    在 String 和适配对象之间转换的 XmlAdapter 如下所示:

    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();
        }
    
    }
    

    可以使用以下演示代码:

    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);
        }
    
    }
    

    生成/使用以下 XML:

    <transactionAdd>
        <salesOrderAdd/>
    </transactionAdd>
    

    欲了解更多信息,请参阅:

    【讨论】:

    • 太棒了!谢谢@Blaise,我读过你的博客,但错过了这篇文章。现在我看到它可以帮助我。再写一张小纸条。我可以使用 new JAXBElement(new QName("SalesOrderLineAdd"), LineAdd.class, lineAddObject) 作为适配器中 marshal 方法的输出吗?
    • @Vladimir:我已经用代码示例更新了我的答案。诀窍是同时使用@XmlJavaTypeAdapter 和@XmlElementRef。
    • @Blaise,假设我想用对象 b 替换对象 a。但我希望对象 b 是具有另一个顶级元素名称但内容相同的对象 a。是否可以使用某种特殊的包装器来做到这一点。类似于:新的 JAXBWrapper("b", a)?
    • @Blaise,我用示例代码更新了问题,以创建事务和示例输出。我想做一些稍微不同的事情——我想根据该对象的一个​​属性命名可映射对象的顶级元素名称。请你看一下。
    • 我已经根据更新开始了这个问题的新答案:stackoverflow.com/questions/4441692/…。我不想删除这个例子,因为我相信它对社区有价值。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2018-09-29
    • 2013-07-05
    • 2021-11-24
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多