Java / JAXB: Unmarshall Xml a una subclase específica basada en un atributo

¿Es posible usar JAXB para unmarshall xml a una clase Java específica basada en un atributo del xml?

    

Me gustaría tener un objeto List of Shape que contenga un triángulo y un cuadrado, cada uno con su propio atributo específico de forma. ES DECIR:

 abstract class Shape { int points; //...etc } class Square extends Shape { String square-specific-attribute; //...etc } class Triangle extends Shape { String triangle-specific-attribute; //...etc } 

Actualmente estoy poniendo todos los atributos en una gran clase de “Forma” y es menos que ideal.

Podría hacer que esto funcione si las formas se llamaban correctamente elementos xml, pero desafortunadamente no tengo control del xml que estoy recuperando.

¡Gracias!

JAXB es una especificación, las implementaciones específicas proporcionarán puntos de extensión para hacer cosas como esta. Si está utilizando EclipseLink JAXB (MOXy) , puede modificar la clase Shape de la siguiente manera:

 import javax.xml.bind.annotation.XmlAttribute; import org.eclipse.persistence.oxm.annotations.XmlCustomizer; @XmlCustomizer(ShapeCustomizer.class) public abstract class Shape { int points; @XmlAttribute public int getPoints() { return points; } public void setPoints(int points) { this.points = points; } } 

Luego, usando el MOXy @XMLCustomizer, puede acceder a InheritancePolicy y cambiar el campo del indicador de clase de “@xsi: type” a “type”:

 import org.eclipse.persistence.config.DescriptorCustomizer; import org.eclipse.persistence.descriptors.ClassDescriptor; public class ShapeCustomizer implements DescriptorCustomizer { @Override public void customize(ClassDescriptor descriptor) throws Exception { descriptor.getInheritancePolicy().setClassIndicatorFieldName("@type"); } } 

Deberá asegurarse de tener un archivo jaxb.properties con sus clases de modelo (Shape, Square, etc.) con la siguiente entrada que especifica la implementación de EclipseLink MOXy JAXB:

 javax.xml.bind.context.factory=org.eclipse.persistence.jaxb.JAXBContextFactory 

A continuación está el rest de las clases modelo:

Formas

 import java.util.ArrayList; import java.util.List; import javax.xml.bind.annotation.XmlRootElement; @XmlRootElement public class Shapes { private List shape = new ArrayList();; public List getShape() { return shape; } public void setShape(List shape) { this.shape = shape; } } 

Cuadrado

 import javax.xml.bind.annotation.XmlAttribute; public class Square extends Shape { private String squareSpecificAttribute; @XmlAttribute(name="square-specific-attribute") public String getSquareSpecificAttribute() { return squareSpecificAttribute; } public void setSquareSpecificAttribute(String s) { this.squareSpecificAttribute = s; } } 

Triángulo

 import javax.xml.bind.annotation.XmlAttribute; public class Triangle extends Shape { private String triangleSpecificAttribute; @XmlAttribute(name="triangle-specific-attribute") public String getTriangleSpecificAttribute() { return triangleSpecificAttribute; } public void setTriangleSpecificAttribute(String t) { this.triangleSpecificAttribute = t; } } 

A continuación se muestra un progtwig de demostración para verificar que todo funcione:

 import java.io.StringReader; 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 jaxbContext = JAXBContext.newInstance(Shapes.class, Triangle.class, Square.class); StringReader xml = new StringReader("43"); Unmarshaller unmarshaller = jaxbContext.createUnmarshaller(); Shapes root = (Shapes) unmarshaller.unmarshal(xml); Marshaller marshaller = jaxbContext.createMarshaller(); marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true); marshaller.marshal(root, System.out); } } 

Espero que esto ayude.

Para obtener más información sobre EclipseLink MOXy, consulte:

EDITAR

En EclipseLink 2.2 lo hacemos más fácil de configurar, consulte el siguiente artículo para obtener más información:

La anotación @XmlElements le permite especificar qué etiqueta corresponde a qué subclase.

 @XmlElements({ @XmlElement(name="square", type=Square.class), @XmlElement(name="triangle", type=Triangle.class) }) public List getShape() { return shape; } 

También vea javadoc para @XmlElements

AFAIK, tendrás que escribir un XmlAdapter que sepa cómo manejar el Mariscal / Desasignación de la Forma.

No, me temo que no es una opción, JAXB no es tan flexible.

Lo mejor que puedo sugerir es que pongas un método en la clase Shape que ejemplifique el tipo “correcto” en función del atributo. El código del cliente invocaría ese método de fábrica para obtenerlo.

Lo mejor que se me ocurre, lo siento.

Hay una anotación @XmlSeeAlso para indicar que se unan las subclases.

Por ejemplo, con las siguientes definiciones de clase:

  class Animal {} class Dog extends Animal {} class Cat extends Animal {} 

El usuario debería crear JAXBContext como JAXBContext.newInstance (Dog.class, Cat.class) (Animal se recogerá automáticamente ya que Dog y Cat se refieren a él).

La anotación XmlSeeAlso te permite escribir:

  @XmlSeeAlso({Dog.class,Cat.class}) class Animal {} class Dog extends Animal {} class Cat extends Animal {}