Java/JAXB: Unmarshall Xml to specific subclass based on an attribute

后端 未结 5 2093

Is it possible to use JAXB to unmarshall xml to a specific Java class based on an attribute of the xml?


  

        
相关标签:
5条回答
  • 2020-11-27 19:25

    JAXB is a spec, specific implementations will provide extension points to do things such as this. If you are using EclipseLink JAXB (MOXy) you could modify the Shape class as follows:

    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;
        }
    
    }
    

    Then using the MOXy @XMLCustomizer you could access the InheritancePolicy and change the class indicator field from "@xsi:type" to just "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");
        }
    }
    

    You will need to ensure that you have a jaxb.properties file in with you model classes (Shape, Square, etc) with the following entry specifying the EclipseLink MOXy JAXB implementation:

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

    Below is the rest of the model classes:

    Shapes

    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;
        }
    
    }
    

    Square

    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;
        }
    
    }
    

    Triangle

    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;
        }
    
    }
    

    Below is a demo program to check that everything works:

    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);
        }
    }
    

    I hope this helps.

    For more information on EclipseLink MOXy see:

    • http://www.eclipse.org/eclipselink/moxy.php

    EDIT

    In EclipseLink 2.2 we're making this easier to configure, check out the following article for more information:

    • http://bdoughan.blogspot.com/2010/11/jaxb-and-inheritance-moxy-extension.html
    0 讨论(0)
  • 2020-11-27 19:25

    The annotation @XmlElements enables you to specify which tag corresponds with which subclass.

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

    Also see javadoc for @XmlElements

    0 讨论(0)
  • 2020-11-27 19:27

    There is @XmlSeeAlso annotation to tell to bind subclasses.

    For example, with the following class definitions:

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

    The user would be required to create JAXBContext as JAXBContext.newInstance(Dog.class,Cat.class) (Animal will be automatically picked up since Dog and Cat refers to it.)

    XmlSeeAlso annotation would allow you to write:

     @XmlSeeAlso({Dog.class,Cat.class})
     class Animal {}
     class Dog extends Animal {}
     class Cat extends Animal {}
    
    0 讨论(0)
  • 2020-11-27 19:28

    No, I'm afraid that's not an option, JAXB isn't that flexible.

    The best I can suggest is that you put a method on the Shape class which instantiates the "correct" type based on the attribute. The client code would invoke that factory method to obtain it.

    Best I can come up with, sorry.

    0 讨论(0)
  • 2020-11-27 19:37

    AFAIK, you'll have to write an XmlAdapter which knows how to handle the marshal/unmarshalling of the Shape.

    0 讨论(0)
提交回复
热议问题