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


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

<shapes>
  <shape type="square" points="4" square-specific-attribute="foo" />
  <shape type="triangle" points="3" triangle-specific-attribute="bar" />
</shapes>

Me gustaría tener una Lista de objetos de Forma que contengan un triángulo y un cuadrado, cada uno con su propio atributo específico de forma. IE:

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 solo 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 llamaran correctamente elementos xml, pero desafortunadamente no tengo el control de el xml que estoy recuperando.

Gracias!

Author: Blaise Doughan, 2010-06-07

5 answers

JAXB es una especificación, implementaciones específicas proporcionarán puntos de extensión para hacer cosas como esta. Si está utilizando EclipseLink JAXB (MOXy) podría modificar la clase de forma 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 la InheritancePolicy y cambiar el campo indicador de clase de "@xsi: type "a solo"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");
    }
}

Tendrá que asegurarse de que tiene un jaxb.archivo de propiedades con sus clases de modelo (Forma, Cuadrado, 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 se muestra el resto 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> shape = new ArrayList<Shape>();;

    public List<Shape> getShape() {
        return shape;
    }

    public void setShape(List<Shape> 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 programa de demostración para comprobar que todo funciona:

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("<shapes><shape square-specific-attribute='square stuff' type='square'><points>4</points></shape><shape triangle-specific-attribute='triangle stuff' type='triangle'><points>3</points></shape></shapes>");
        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 véase:

EDITAR

En EclipseLink 2.2 estamos haciendo esto más fácil de configurar, echa un vistazo al siguiente artículo para obtener más información:

 17
Author: Blaise Doughan,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2010-11-05 20:50:57

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

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

También ver javadoc para @XmlElements

 7
Author: Barmak,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2016-04-26 21:13:34

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

 3
Author: Quotidian,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2010-06-07 18:57:05

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 instancie el tipo "correcto" basado en el atributo. El código del cliente invocaría ese método de fábrica para obtenerlo.

Lo mejor que se me ocurre, lo siento.

 0
Author: skaffman,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2010-06-07 18:56:24

Hay una anotación @XmlSeeAlso para indicar que se deben enlazar subclases.

Por ejemplo, con las siguientes definiciones de clase:

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

El usuario tendría que crear JAXBContext como JAXBContext.newInstance (Dog.clase,Cat.clase) (El animal se recogerá automáticamente ya que el perro y el Gato se refieren a él.)

XmlTamBién la anotación le permitiría escribir:

 @XmlSeeAlso({Dog.class,Cat.class})
 class Animal {}
 class Dog extends Animal {}
 class Cat extends Animal {}
 -2
Author: Molis,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2011-10-29 14:29:59