unmarshal - parse xml java jaxb
JAXB: Cómo evitar la definición repetida del espacio de nombres para xmlns: xsi (8)
Tengo una configuración JAXB donde utilizo @XmlJavaTypeAdapter para reemplazar objetos de tipo Person
con objetos de tipo PersonRef
que solo contienen el UUID de la persona. Esto funciona perfectamente bien. Sin embargo, el XML generado redeclara el mismo espacio de nombres ( xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
) cada vez que se utiliza. Si bien esto generalmente está bien, simplemente no se siente bien.
¿Cómo puedo configurar JAXB para declarar xmlns: xsi al principio del documento? ¿Puedo agregar manualmente declaraciones de espacio de nombres al elemento raíz?
Aquí hay un ejemplo de lo que quiero lograr:
Corriente:
<person uuid="6ec0cf24-e880-431b-ada0-a5835e2a565a">
<relation type="CHILD">
<to xsi:type="personRef" uuid="56a930c0-5499-467f-8263-c2a9f9ecc5a0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"/>
</relation>
<relation type="CHILD">
<to xsi:type="personRef" uuid="6ec0cf24-e880-431b-ada0-a5835e2a565a" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"/>
</relation>
<!-- SNIP: some more relations -->
</person>
Querido:
<person uuid="6ec0cf24-e880-431b-ada0-a5835e2a565a" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<relation type="CHILD">
<to xsi:type="personRef" uuid="56a930c0-5499-467f-8263-c2a9f9ecc5a0"/>
</relation>
<relation type="CHILD">
<to xsi:type="personRef" uuid="6ec0cf24-e880-431b-ada0-a5835e2a565a"/>
</relation>
<!-- SNIP: some more relations -->
</person>
Agregue su mapeo nsPrefix
haciendo esto:
marshaller.setNamespaceMapping("myns","urn:foo");
Es XML, por lo que puede procesar el resultado usando DOM o XSLT para deshacerse de múltiples referencias de espacio de nombres.
Esta es la mejor respuesta que encuentro en la web.
Las declaraciones xsi:type
se crean con mayor probabilidad porque el tipo declarado de JAXBElement
no coincide con el tipo del valor.
Si ObjectFactory
tiene un método de creación para el JAXBElement
correcto, debe usarlo ya que debe completar correctamente tanto QName
como la información de tipo; de lo contrario, intentaría establecer el tipo declarado (segundo arg constructor) del JAXBElement
en String.class
(suponiendo que este sea el tipo de commentTest
) en lugar de CommentType.Comment
.
Propietario: cbrettin
No es muy bonito, pero podría agregar un schemaLocation vacío al elemento raíz:
marshaller.setProperty(Marshaller.JAXB_SCHEMA_LOCATION, "");
Parece un problema de personalización JAXB asignador de espacio de nombres
Cuando gestiona un documento XML utilizando JAXB 1.0, un objeto Marshaller, un objeto JAXB que controla el proceso de clasificación, proporciona declaraciones de espacio de nombres en el documento XML resultante. A veces, el Marshaller produce una gran cantidad de declaraciones de espacios de nombres que parecen redundantes, por ejemplo:
<?xml version="1.0"?>
<root>
<ns1:element xmlns:ns1="urn:foo"> ... </ns1:element>
<ns2:element xmlns:ns2="urn:foo"> ... </ns2:element>
<ns3:element xmlns:ns3="urn:foo"> ... </ns3:element>
</root>
JAXB 2.0 cambia este comportamiento. Si utiliza JAXB 2.0 (o posterior) para ordenar un documento XML, el Marshaller declara todos los identificadores de recursos uniformes (URI) del espacio de nombres conocidos estadísticamente, es decir, aquellos URI que se utilizan como nombres de elementos o atributos en las anotaciones de JAXB.
JAXB también puede declarar espacios de nombres adicionales en el medio de un documento XML, por ejemplo, cuando un nombre calificado (
QName
) que se usa como valor de atributo o elemento requiere un nuevo URI de espacio de nombres o cuando un nodo Modelo de objetos de documento (DOM) en un el árbol de contenido requiere un nuevo URI de espacio de nombres. Este comportamiento puede producir un documento XML que tiene muchas declaraciones de espacios de nombres con prefijos de espacio de nombres generados automáticamente.El problema es que los prefijos de espacio de nombres generados automáticamente, como ns1, ns2 y ns3, no son fáciles de usar; por lo general, no ayudan a las personas a entender el XML ordenado.
Afortunadamente, JAXB 2.0 (o posterior) proporciona una interfaz de proveedor de servicio (SPI) llamada
com.sun.xml.bind.marshaller.NamespacePrefixMapper
que puede usar para especificar prefijos de espacio de nombres más útiles para la clasificación.Cuando el programa JAXBSample coordina el documento XML por primera vez, lo hace sin utilizar una clase
NamespacePrefixMapper
. Como resultado, Marshaller genera automáticamente un prefijo de espacio de nombres, en este caso, ns2.
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<ns2:JustAnElement xmlns:ns2="a">
<foo>true</foo>
</ns2:JustAnElement>
Ejemplo de una configuración que evita la repetición del espacio de nombres:
El segundo marshalling realizado por el programa
JAXBSample
utiliza una claseNamespacePrefixMapper
la siguiente manera:
NamespacePrefixMapper m = new PreferredMapper();
marshal(jc, e, m);
public static class PreferredMapper extends NamespacePrefixMapper {
@Override
public String getPreferredPrefix(String namespaceUri, String suggestion, boolean requirePrefix) {
return "mappedNamespace" + namespaceUri;
}
}
El método
getPreferredPrefix()
en la clasePreferredMapper
devuelve el prefijo preferido, en este caso,mappedNamespacea
para ser declarado en el elemento raíz del XML marshalled.
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<mappedNamespacea:JustAnElement xmlns:mappedNamespacea="a">
<foo>true</foo>
</mappedNamespacea:JustAnElement>
Puede dejar que los espacios de nombres se escriban solo una vez. Necesitará una clase proxy de XMLStreamWriter y un paquete-info.java. Entonces lo harás en tu código:
StringWriter stringWriter = new StringWriter();
XMLStreamWriter writer = new Wrapper((XMLStreamWriter) XMLOutputFactory
.newInstance().createXMLStreamWriter(stringWriter));
JAXBContext jaxbContext = JAXBContext.newInstance(Collection.class);
Marshaller jaxbMarshaller = jaxbContext.createMarshaller();
jaxbMarshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, Boolean.TRUE);
jaxbMarshaller.marshal(books, writer);
System.out.println(stringWriter.toString());
Clase de proxy (el método más importante es "writeNamespace"):
class WrapperXMLStreamWriter implements XMLStreamWriter {
private final XMLStreamWriter writer;
public WrapperXMLStreamWriter(XMLStreamWriter writer) {
this.writer = writer;
}
//keeps track of what namespaces were used so that not to
//write them more than once
private List<String> namespaces = new ArrayList<String>();
public void init(){
namespaces.clear();
}
public void writeStartElement(String localName) throws XMLStreamException {
init();
writer.writeStartElement(localName);
}
public void writeStartElement(String namespaceURI, String localName) throws XMLStreamException {
init();
writer.writeStartElement(namespaceURI, localName);
}
public void writeStartElement(String prefix, String localName, String namespaceURI) throws XMLStreamException {
init();
writer.writeStartElement(prefix, localName, namespaceURI);
}
public void writeNamespace(String prefix, String namespaceURI) throws XMLStreamException {
if(namespaces.contains(namespaceURI)){
return;
}
namespaces.add(namespaceURI);
writer.writeNamespace(prefix, namespaceURI);
}
// .. other delegation method, always the same pattern: writer.method() ...
}
package-info.java:
@XmlSchema(elementFormDefault=XmlNsForm.QUALIFIED, attributeFormDefault=XmlNsForm.UNQUALIFIED ,
xmlns = {
@XmlNs(namespaceURI = "http://www.w3.org/2001/XMLSchema-instance", prefix = "xsi")})
package your.package;
import javax.xml.bind.annotation.XmlNs;
import javax.xml.bind.annotation.XmlNsForm;
import javax.xml.bind.annotation.XmlSchema;
Puedes hacerlo con el código:
marshaller.setProperty("com.sun.xml.bind.namespacePrefixMapper", new NamespacePrefixMapper() {
@Override
public String[] getPreDeclaredNamespaceUris() {
return new String[] {
XMLConstants.W3C_XML_SCHEMA_INSTANCE_NS_URI
};
}
@Override
public String getPreferredPrefix(String namespaceUri, String suggestion, boolean requirePrefix) {
if (namespaceUri.equals(XMLConstants.W3C_XML_SCHEMA_INSTANCE_NS_URI))
return "xsi";
if (namespaceUri.equals(XMLConstants.W3C_XML_SCHEMA_NS_URI))
return "xs";
if (namespaceUri.equals(WellKnownNamespace.XML_MIME_URI))
return "xmime";
return suggestion;
}
});
si estás usando Maven, solo agrega esto a tu pom:
<dependency>
<groupId>com.sun.xml.bind</groupId>
<artifactId>jaxb-impl</artifactId>
<version>2.2.2</version>
<type>jar</type>
<scope>compile</scope>
</dependency>
no necesita PreferredMapper si configura sus anotaciones como se define en el ejemplo anterior. Aunque tengo un archivo package-info.jave confunde de la siguiente manera:
@javax.xml.bind.annotation.XmlSchema(
namespace = "mylovelynamespace1",
xmlns = {
@javax.xml.bind.annotation.XmlNs(prefix = "myns1", namespaceURI = "mylovelynamespace1"),
@javax.xml.bind.annotation.XmlNs(prefix = "myns2", namespaceURI = "mylovelynamespace2")
},
elementFormDefault = javax.xml.bind.annotation.XmlNsForm.QUALIFIED)
package com.mylovelycompanyname.package;