JAXB marshalling purely from interfaces

前端 未结 2 1600
甜味超标
甜味超标 2021-02-13 14:05

I have a complex hierarchy of Java interfaces that I\'d like to marshal (and not necessarily unmarshal) with JAXB. These interfaces represent objects that will be returned from

相关标签:
2条回答
  • 2021-02-13 14:43

    I think the answer is that JAXB isn't at all intended to support this and that it's foolish to try to force it. Also, JAXB-driven JSON marshaling turns out not to be ideal either.

    I ended up writing my own marshaling framework, with its own set of annotations:

    @MarshalMixin // marshal fields but not a top-level object
    interface Uuided {
      @MarshalAsString // ignore properties; just call toString()
      @MarshalId // treat as identifier for @MarshalUsingIds or cyclic ref
      UUID getId();
    }
    @MarshalMixin
    interface Named {
      @MarshalId
      String getName();
    }
    @MarshalObject // top-level marshaled object providing class name
    interface Component extends Uuided, Named {
      @MarshalAsKeyedObjectMap(key = "name") // see description below
      Map<String, ComponentAttribute> getAttributes();
    }
    @MarshalObject
    interface Attribute extends Named {
      Type getType();
      @MarshalDynamic // use run-time (not declared) type
      Object getValue();
    }
    interface ComponentAttribute extends Attribute {
      @MarshalUsingIds
      Component getDeclaringComponent();
    }
    

    The generated marshalers write to an abstraction layer (currently implemented for JSON and XML). This gives a lot more flexibility to make the output natural for different representations without a ton of annotations and adapters. E.g., what I'm calling keyed-object maps, where each object contains its map key. In JSON, you want a map, but in XML, you want a sequence:

    {..., map: {'a': {'name': 'a', ...}, 'b': {'name: 'b', ...}, ...}, ...}
    ...<map><thing name='a'>...</thing><thing name='b'>...</thing></map>...
    

    Seems like as many as 4 other people care about this too, so hopefully I can open-source it eventually. :-)

    0 讨论(0)
  • 2021-02-13 15:06

    Short answer: use @XmlElement(type = Object.class) on your interface field.

    Details below:

    I have found 2 ways in which you could make JAXB serialize your interfaces:

    1. @XmlAnyElement
    2. @XmlElement(type = Object.class)

    1.@XmlAnyElement

    Simply annotate your interface type field with @XmlAnyElement and JAXB will serialize the interface from it's concrete type. Don't forget to annotate the concrete types with @XmlRootElement and to add the concrete types to the JAXBContext. Full example follows:

    public class InterfaceSerializer {
    
        @XmlRootElement
        public static class Pojo {
            Pojo() {
                field1 = new PojoFieldImpl1();
                field2 = new PojoFieldImpl2();
                field3 = new PojoFieldImpl1();
            }
    
            @XmlAnyElement
            public IPojoField field1;
            @XmlAnyElement
            public IPojoField field2;
            @XmlAnyElement
            public IPojoField field3;
    
            @Override
            public String toString() {
                return "field1 = " + field1 + "\nfield2 = " + field2 + "\nfield3 = " + field3;
            }
        }
    
        public static interface IPojoField {
    
        }
    
        @XmlRootElement
        public static class PojoFieldImpl1 implements IPojoField {
    
            PojoFieldImpl1() {
                value = "PojoFieldImpl1 value";
            }
    
            public String value;
    
            @Override
            public String toString() {
                return value;
            }
        }
    
        @XmlRootElement
        public static class PojoFieldImpl2  implements IPojoField {
    
            PojoFieldImpl2() {
                value = "PojoFieldImpl2 value1";
                value2 = "PojoFieldImpl2 value2";
            }
    
            public String value;
            public String value2;
    
            @Override
            public String toString() {
                return value + " " + value2;
            }
        }
    
        public static void main(String []args) throws JAXBException {
            Pojo pojo = new Pojo();
            JAXBContext jaxbContext = JAXBContext.newInstance(Pojo.class, PojoFieldImpl1.class, PojoFieldImpl2.class);
            Marshaller marshaller = jaxbContext.createMarshaller();
    
            marshaller.marshal(pojo, new File("interfaceSerializer.xml"));
        }
    }
    

    Output XML:

    <?xml version="1.0" encoding="UTF-8" standalone="yes"?>
    <pojo>
        <pojoFieldImpl1>
            <value>PojoFieldImpl1 value</value>
        </pojoFieldImpl1>
        <pojoFieldImpl2>
            <value>PojoFieldImpl2 value1</value>
            <value2>PojoFieldImpl2 value2</value2>
        </pojoFieldImpl2>
        <pojoFieldImpl1>
            <value>PojoFieldImpl1 value</value>
        </pojoFieldImpl1>
    </pojo>
    

    The downsides of this method:

    • you can't distinguish from XML each individual field from your pojo (same implementations will be written with the same tag)
    • you don't have any type information to Unmarshall your XML (if you would wish to do so)

    These downsides are fixed in the second solution:

    2.@XmlElement(type = Object.class)

    I've stumbled upon this in mikesir87's blog post. Simply replace the @XmlAnyElement annotations from above with @XmlElement(type = Object.class) You should have something like this in the Pojo class from above:

    @XmlElement(type = Object.class)
    public IPojoField field1;
    @XmlElement(type = Object.class)
    public IPojoField field2;
    @XmlElement(type = Object.class)
    public IPojoField field3;
    

    Re-running our example, the resulting XML:

    <?xml version="1.0" encoding="UTF-8" standalone="yes"?>
    <pojo>
        <field1 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:type="pojoFieldImpl1">
            <value>PojoFieldImpl1 value</value>
        </field1>
        <field2 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:type="pojoFieldImpl2">
            <value>PojoFieldImpl2 value1</value>
            <value2>PojoFieldImpl2 value2</value2>
        </field2>
        <field3 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:type="pojoFieldImpl1">
            <value>PojoFieldImpl1 value</value>
        </field3>
    </pojo>
    

    This can also be deserialized:

    Unmarshaller unmarshaller = jaxbContext.createUnmarshaller();
    Pojo unmarshalledPojo = (Pojo) unmarshaller.unmarshal(new File("interfaceSerializer.xml"));  
    System.out.println(unmarshalledPojo);
    

    Resulting output:

    field1 = PojoFieldImpl1 value
    field2 = PojoFieldImpl2 value1 PojoFieldImpl2 value2
    field3 = PojoFieldImpl1 value
    

    Probably a "hackish" solution, but it gets the job done.

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