jaxbunmarshaller - parse xml java jaxb
JAXB: ¿cómo separar una Lista de objetos de diferentes tipos pero con un elemento primario común? (3)
No creo que esto sea posible si todos los elementos se nombran <fee>
. Incluso si fuera (o lo es) sería muy confuso desde el punto de vista del mantenimiento.
¿Tiene la capacidad de cambiar el nombre de varios elementos de tarifa según el tipo (por ejemplo, <tradeFee>
lugar de <fee>
)?
De lo contrario, puede crear una clase BaseFee
que tenga todos los campos para cada tipo posible de <fee>
. Puede deshacer los datos en una lista de objetos BaseFee
y convertirlos en un tipo más específico en tiempo de ejecución, por ejemplo
List<BaseFee> fees = ...;
for (BaseFee fee : fees) {
if (isTradeFee(fee)) {
TradeFee tradeFee = toTradeFee(fee);
// do something with trade fee...
}
}
Un poco hack pero teniendo en cuenta los requisitos, debería hacer el trabajo.
Hay un patrón bastante común en nuestras aplicaciones. Configuramos una configuración de un conjunto (o lista) de objetos en Xml, que implementan una interfaz común. Al iniciar, la aplicación lee el Xml y utiliza JAXB para crear / configurar una Lista de objetos. Nunca he descubierto (después de leer varias publicaciones muchas veces) la "forma correcta" de hacerlo solo con JAXB.
Por ejemplo, tenemos una Fee
interfaz y varias clases de implementación concretas que tienen algunas propiedades comunes, así como algunas propiedades divergentes y comportamientos muy diferentes. El Xml que utilizamos para configurar la Lista de tarifas utilizada por la aplicación es:
<fees>
<fee type="Commission" name="commission" rate="0.000125" />
<fee type="FINRAPerShare" name="FINRA" rate="0.000119" />
<fee type="SEC" name="SEC" rate="0.0000224" />
<fee type="Route" name="ROUTES">
<routes>
<route>
<name>NYSE</name>
<rates>
<billing code="2" rate="-.0014" normalized="A" />
<billing code="1" rate=".0029" normalized="R" />
</rates>
</route>
</routes>
...
</fee>
</fees>
En el Xml anterior, cada elemento <fee>
corresponde a una subclase concreta de una interfaz Fee. El atributo de type
proporciona información sobre qué tipo crear una instancia y, una vez que se crea una instancia, el desemparejamiento de JAXB aplica las propiedades del Xml restante.
Siempre tengo que recurrir a hacer algo como esto:
private void addFees(TradeFeeCalculator calculator) throws Exception {
NodeList feeElements = configDocument.getElementsByTagName("fee");
for (int i = 0; i < feeElements.getLength(); i++) {
Element feeElement = (Element) feeElements.item(i);
TradeFee fee = createFee(feeElement);
calculator.add(fee);
}
}
private TradeFee createFee(Element feeElement) {
try {
String type = feeElement.getAttribute("type");
LOG.info("createFee(): creating TradeFee for type=" + type);
Class<?> clazz = getClassFromType(type);
TradeFee fee = (TradeFee) JAXBConfigurator.createAndConfigure(clazz, feeElement);
return fee;
} catch (Exception e) {
throw new RuntimeException("Trade Fees are misconfigured, xml which caused this=" + XmlUtils.toString(feeElement), e);
}
}
En el código anterior, el JAXBConfigurator
es solo un contenedor simple alrededor de los objetos JAXB para desasignar:
public static Object createAndConfigure(Class<?> clazz, Node startNode) {
try {
JAXBContext context = JAXBContext.newInstance(clazz);
Unmarshaller unmarshaller = context.createUnmarshaller();
@SuppressWarnings("rawtypes")
JAXBElement configElement = unmarshaller.unmarshal(startNode, clazz);
return configElement.getValue();
} catch (JAXBException e) {
throw new RuntimeException(e);
}
}
Al final, del código anterior, obtenemos una lista que contiene los tipos que se configuraron en el Xml.
¿Hay alguna manera de hacer que JAXB haga esto automáticamente sin tener que escribir el código para repetir los elementos como se indicó anteriormente?
Puede usar un XmlAdapter
para este caso de uso. La versión anterior maneja solo el tipo de Commission
pero podría ampliarse fácilmente para admitir todos los tipos. AdaptedFee
asegurarse de que AdaptedFee
contenga las propiedades combinadas de todas las implementaciones de la interfaz de Fee
.
import javax.xml.bind.annotation.XmlAttribute;
import javax.xml.bind.annotation.adapters.XmlAdapter;
public class FeeAdapter extends XmlAdapter<FeeAdapter.AdaptedFee, Fee>{
public static class AdaptedFee {
@XmlAttribute
public String type;
@XmlAttribute
public String name;
@XmlAttribute
public String rate;
}
@Override
public AdaptedFee marshal(Fee fee) throws Exception {
AdaptedFee adaptedFee = new AdaptedFee();
if(fee instanceof Commission) {
Commission commission = (Commission) fee;
adaptedFee.type = "Commission";
adaptedFee.name = commission.name;
adaptedFee.rate = commission.rate;
}
return adaptedFee;
}
@Override
public Fee unmarshal(AdaptedFee adaptedFee) throws Exception {
if("Commission".equals(adaptedFee.type)) {
Commission commission = new Commission();
commission.name = adaptedFee.name;
commission.rate = adaptedFee.rate;
return commission;
}
return null;
}
}
Un XmlAdapter
se configura utilizando la anotación @XmlJavaTypeAdapter
:
import java.util.List;
import javax.xml.bind.annotation.*;
import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter;
@XmlRootElement
@XmlAccessorType(XmlAccessType.FIELD)
public class Fees {
@XmlElement(name="fee")
@XmlJavaTypeAdapter(FeeAdapter.class)
private List<Fee> fees;
}
Para más información
Nota: Soy el líder de EclipseLink JAXB (MOXy) y miembro del grupo de expertos JAXB (JSR-222) .
Si está utilizando MOXy como su proveedor JAXB, puede usar la anotación @XmlPaths de @XmlPaths
para extender la anotación JAXB @XmlElements
estándar para hacer lo siguiente:
Matrícula
import java.util.List;
import javax.xml.bind.annotation.*;
import org.eclipse.persistence.oxm.annotations.*;
@XmlRootElement
public class Fees {
@XmlElements({
@XmlElement(type=Commission.class),
@XmlElement(type=FINRAPerShare.class),
@XmlElement(type=SEC.class),
@XmlElement(type=Route.class)
})
@XmlPaths({
@XmlPath("fee[@type=''Commission'']"),
@XmlPath("fee[@type=''FINRAPerShare'']"),
@XmlPath("fee[@type=''SEC'']"),
@XmlPath("fee[@type=''Route'']")
})
private List<Fee> fees;
}
Comisión
Las implementaciones de la interfaz de Fee
se anotarán normalmente.
import javax.xml.bind.annotation.*;
@XmlAccessorType(XmlAccessType.FIELD)
public class Commission implements Fee {
@XmlAttribute
private String name;
@XmlAttribute
private String rate;
}
Para más información