JAXB:marshal/unmarshal 个不同的 XML 个元素 to/from 个 class
JAXB: marshal/unmarshal different XML elements to/from one class
比方说,我有以下 class:
@XmlRootElement
class Payment {
@XmlElement
int amount;
@XmlElement
Currency currency;
@XmlElement
IResponse response;
}
如果 response==null
- 元素是 "request",否则 - 元素是 "response"。当一个请求时,该元素应该被(取消)编组到(来自)称为 PaymentRequest
的根元素,当响应时 - 到(来自)PaymentResponse
.
如何配置这样的编组算法?如果 JAXB 做不到,也许其他引擎可以?
像这样创建和编组根元素:
JAXBElement<Payment> jbe;
if( payment.getResponse() != null ){
jbe = wrap( null, "PaymentResponse", payment );
} else {
jbe = wrap( null, "PaymentRequest", payment );
}
m.marshal( jbe, sw );
使用简单的辅助方法
<T> JAXBElement<T> wrap( String ns, String tag, T o ){
QName qtag = new QName( ns, tag );
Class<?> clazz = o.getClass();
@SuppressWarnings( "unchecked" )
JAXBElement<T> jbe = new JAXBElement( qtag, clazz, o );
return jbe;
}
一种使解组成为可能的简单方法是创建两个子类 PaymentResponse 和 PaymentRequest,它们充当@XmlRootElements。 ObjectFactory 包含
@XmlElementDecl(namespace = "", name = "PaymentRequest")
public JAXBElement<PaymentRequest>
createPaymentRequest(PaymentRequest value) {
return new JAXBElement<PaymentRequest>(_Payment_QNAME, PaymentRequest.class, null, value);
}
@XmlElementDecl(namespace = "", name = "PaymentResponse")
public JAXBElement<PaymentResponse>
createPaymentResponse(PaymentResponse value) {
return new JAXBElement<PaymentResponse>(_Payment_QNAME, PaymentResponse.class, null, value);
}
解组:
JAXBContext jc = JAXBContext.newInstance( PACKAGE );
Unmarshaller m = jc.createUnmarshaller();
JAXBElement<?> tb = null;
try {
Payment payment = readFrom( Payment.class );
} catch( Exception e ){
}
和方法 readFrom:
public <T> T readFrom( Class<T> type ) throws Exception {
try {
JAXBContext jc = JAXBContext.newInstance( PACKAGE );
Unmarshaller u = jc.createUnmarshaller();
JAXBElement<T> jbe = (JAXBElement<T>)u.unmarshal( new File( XMLIN ) );
return type.cast( jbe.getValue() );
} catch (JAXBException e) {
e.printStackTrace();
}
return null;
}
我终于通过拦截 StAX 事件实现了解组。这是代码:
JAXBContext jc = JAXBContext.newInstance(RootElement.class, A.class, B.class, C.class, D.class, E.class);
Unmarshaller unmarsh = jc.createUnmarshaller();
XMLStreamReader xs = XMLInputFactory.newInstance().createXMLStreamReader(new StringReader(etalonRs));
XMLStreamReader xd = new StreamReaderDelegate(xs) {
public static final String ROOT_ELEMENT = "TestRoot";
public static final int REPLACEABLE_LEVEL = 2;
public final Collection<String> sufficesToDelete = Arrays.asList("Rq", "Rs");
protected Stack<String> elementNamesStack = new Stack<>();
protected Set<String> replaceableNames = new HashSet<>();
@Override
public String getLocalName() {
String realName = super.getLocalName();
if (replaceableNames.contains(realName) && elementNamesStack.size() == REPLACEABLE_LEVEL) {
for (String suffix : sufficesToDelete) {
if (realName.endsWith(suffix)) {
return realName.substring(0, realName.lastIndexOf(suffix));
}
}
}
return realName;
}
@Override
public int next() throws XMLStreamException {
final int eventCode = super.next();
processLevel(eventCode);
return eventCode;
}
@Override
public int nextTag() throws XMLStreamException {
final int eventCode = super.nextTag();
processLevel(eventCode);
return eventCode;
}
private void processLevel(int eventCode) {
switch (eventCode) {
case XMLStreamReader.START_ELEMENT:
final String origElementName = super.getLocalName();
if ((elementNamesStack.size() + 1) == REPLACEABLE_LEVEL && elementNamesStack.peek().equals(ROOT_ELEMENT))
replaceableNames.add(origElementName);
elementNamesStack.push(origElementName);
break;
case XMLStreamReader.END_ELEMENT:
assert(elementNamesStack.pop().equals(super.getLocalName()));
break;
}
}
};
Object o = unmarsh.unmarshal(xd);
这是我的测试类。是的,生产中的实际结构更复杂 - 有不同的 "payments" 并且它们的元素不在根目录中,所以我不得不使用 @XmlAnyElement
注释:
@XmlAccessorType(XmlAccessType.FIELD)
@XmlRootElement(name = "TestRoot")
public static class RootElement {
@XmlElement(name = "SomeDate")
private Date dt = new Date();
@XmlAnyElement(lax=true)
private A a = new C();
}
@XmlAccessorType(XmlAccessType.FIELD)
@XmlRootElement(name = "TestA")
@XmlType
public static abstract class A {
private int fld1 = 1;
@XmlAnyElement(lax=true)
@XmlElementWrapper
protected List<Object> list = new ArrayList<>();
}
@XmlAccessorType(XmlAccessType.FIELD)
@XmlRootElement(name = "TestC")
public static class C extends A {
private int fld2 = 3;
}
封送处理可以用相同的方式实现,但您必须从头开始编写 "StreamWriterDelegate"。
比方说,我有以下 class:
@XmlRootElement
class Payment {
@XmlElement
int amount;
@XmlElement
Currency currency;
@XmlElement
IResponse response;
}
如果 response==null
- 元素是 "request",否则 - 元素是 "response"。当一个请求时,该元素应该被(取消)编组到(来自)称为 PaymentRequest
的根元素,当响应时 - 到(来自)PaymentResponse
.
如何配置这样的编组算法?如果 JAXB 做不到,也许其他引擎可以?
像这样创建和编组根元素:
JAXBElement<Payment> jbe;
if( payment.getResponse() != null ){
jbe = wrap( null, "PaymentResponse", payment );
} else {
jbe = wrap( null, "PaymentRequest", payment );
}
m.marshal( jbe, sw );
使用简单的辅助方法
<T> JAXBElement<T> wrap( String ns, String tag, T o ){
QName qtag = new QName( ns, tag );
Class<?> clazz = o.getClass();
@SuppressWarnings( "unchecked" )
JAXBElement<T> jbe = new JAXBElement( qtag, clazz, o );
return jbe;
}
一种使解组成为可能的简单方法是创建两个子类 PaymentResponse 和 PaymentRequest,它们充当@XmlRootElements。 ObjectFactory 包含
@XmlElementDecl(namespace = "", name = "PaymentRequest")
public JAXBElement<PaymentRequest>
createPaymentRequest(PaymentRequest value) {
return new JAXBElement<PaymentRequest>(_Payment_QNAME, PaymentRequest.class, null, value);
}
@XmlElementDecl(namespace = "", name = "PaymentResponse")
public JAXBElement<PaymentResponse>
createPaymentResponse(PaymentResponse value) {
return new JAXBElement<PaymentResponse>(_Payment_QNAME, PaymentResponse.class, null, value);
}
解组:
JAXBContext jc = JAXBContext.newInstance( PACKAGE );
Unmarshaller m = jc.createUnmarshaller();
JAXBElement<?> tb = null;
try {
Payment payment = readFrom( Payment.class );
} catch( Exception e ){
}
和方法 readFrom:
public <T> T readFrom( Class<T> type ) throws Exception {
try {
JAXBContext jc = JAXBContext.newInstance( PACKAGE );
Unmarshaller u = jc.createUnmarshaller();
JAXBElement<T> jbe = (JAXBElement<T>)u.unmarshal( new File( XMLIN ) );
return type.cast( jbe.getValue() );
} catch (JAXBException e) {
e.printStackTrace();
}
return null;
}
我终于通过拦截 StAX 事件实现了解组。这是代码:
JAXBContext jc = JAXBContext.newInstance(RootElement.class, A.class, B.class, C.class, D.class, E.class);
Unmarshaller unmarsh = jc.createUnmarshaller();
XMLStreamReader xs = XMLInputFactory.newInstance().createXMLStreamReader(new StringReader(etalonRs));
XMLStreamReader xd = new StreamReaderDelegate(xs) {
public static final String ROOT_ELEMENT = "TestRoot";
public static final int REPLACEABLE_LEVEL = 2;
public final Collection<String> sufficesToDelete = Arrays.asList("Rq", "Rs");
protected Stack<String> elementNamesStack = new Stack<>();
protected Set<String> replaceableNames = new HashSet<>();
@Override
public String getLocalName() {
String realName = super.getLocalName();
if (replaceableNames.contains(realName) && elementNamesStack.size() == REPLACEABLE_LEVEL) {
for (String suffix : sufficesToDelete) {
if (realName.endsWith(suffix)) {
return realName.substring(0, realName.lastIndexOf(suffix));
}
}
}
return realName;
}
@Override
public int next() throws XMLStreamException {
final int eventCode = super.next();
processLevel(eventCode);
return eventCode;
}
@Override
public int nextTag() throws XMLStreamException {
final int eventCode = super.nextTag();
processLevel(eventCode);
return eventCode;
}
private void processLevel(int eventCode) {
switch (eventCode) {
case XMLStreamReader.START_ELEMENT:
final String origElementName = super.getLocalName();
if ((elementNamesStack.size() + 1) == REPLACEABLE_LEVEL && elementNamesStack.peek().equals(ROOT_ELEMENT))
replaceableNames.add(origElementName);
elementNamesStack.push(origElementName);
break;
case XMLStreamReader.END_ELEMENT:
assert(elementNamesStack.pop().equals(super.getLocalName()));
break;
}
}
};
Object o = unmarsh.unmarshal(xd);
这是我的测试类。是的,生产中的实际结构更复杂 - 有不同的 "payments" 并且它们的元素不在根目录中,所以我不得不使用 @XmlAnyElement
注释:
@XmlAccessorType(XmlAccessType.FIELD)
@XmlRootElement(name = "TestRoot")
public static class RootElement {
@XmlElement(name = "SomeDate")
private Date dt = new Date();
@XmlAnyElement(lax=true)
private A a = new C();
}
@XmlAccessorType(XmlAccessType.FIELD)
@XmlRootElement(name = "TestA")
@XmlType
public static abstract class A {
private int fld1 = 1;
@XmlAnyElement(lax=true)
@XmlElementWrapper
protected List<Object> list = new ArrayList<>();
}
@XmlAccessorType(XmlAccessType.FIELD)
@XmlRootElement(name = "TestC")
public static class C extends A {
private int fld2 = 3;
}
封送处理可以用相同的方式实现,但您必须从头开始编写 "StreamWriterDelegate"。