javagenericsjaxbxml-namespacesxsi

Remove xsi:type, xmlns:xs, and xmlns:xsi from JAXB Generics


When using JAXB, I'd like to remove the excess namespaces/types from my XML elements when using Generics. How can I do this or what am I doing wrong? I'd like to use Generics so that I only have to write a block of code once.

Example code:

public static void main(String[] args) {
    try {
        TestRoot root = new TestRoot();
        root.name.value = "bobby";
        root.age.value = 102;
        root.color.value = "blue";

        JAXBContext context = JAXBContext.newInstance(root.getClass());
        Marshaller marsh = context.createMarshaller();
        marsh.setProperty(Marshaller.JAXB_ENCODING,"UTF-8");
        marsh.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT,Boolean.TRUE);

        StringWriter sw = new StringWriter();
        PrintWriter pw = new PrintWriter(sw);
        marsh.marshal(root,pw);
        System.out.println(sw.toString());
    }
    catch(Throwable t) {
        t.printStackTrace();
    }
}

@XmlRootElement
static class TestRoot {
    @XmlElement public TestGeneric<String> name = new TestGeneric<String>(true);
    @XmlElement public TestGeneric<Integer> age = new TestGeneric<Integer>(true);
    @XmlElement public TestWhatIWouldLike color = new TestWhatIWouldLike(true);
}

@XmlAccessorType(XmlAccessType.NONE)
static class TestGeneric<T> {
    @XmlAttribute public boolean isRequired;
    @XmlElement public T value;

    public TestGeneric() {
    }

    public TestGeneric(boolean isRequired) {
        this.isRequired = isRequired;
    }
}

@XmlAccessorType(XmlAccessType.NONE)
static class TestWhatIWouldLike {
    @XmlAttribute public boolean isRequired;
    @XmlElement public String value;

    public TestWhatIWouldLike() {
    }

    public TestWhatIWouldLike(boolean isRequired) {
        this.isRequired = isRequired;
    }
}

Output:

<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<testRoot>
    <name isRequired="true">
        <value xsi:type="xs:string" xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">bobby</value>
    </name>
    <age isRequired="true">
        <value xsi:type="xs:int" xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">102</value>
    </age>
    <color isRequired="true">
        <value>blue</value>
    </color>
</testRoot>

Solution

  • Test Generic

    Your JAXB (JSR-222) implementation is going to create mappings per class (i.e. TestGeneric, not type (i.e. TestGeneric<Integer>). As such it is going to treat the value field as type Object. This will result in the xsi:type attributes as your JAXB implementation is adding enough information to be able to unmarshal the same types.

    @XmlAccessorType(XmlAccessType.NONE)
    static class TestGeneric<T> {
        @XmlAttribute public boolean isRequired;
        @XmlElement public T value;
    
        public TestGeneric() {
        }
    
        public TestGeneric(boolean isRequired) {
            this.isRequired = isRequired;
        }
    }
    

    Demo

    Below is an approach you could use. I have introduced subclasses of TestGeneric to represent the different possible types.

    package forum11192623;
    
    import java.io.PrintWriter;
    import java.io.StringWriter;
    
    import javax.xml.bind.*;
    import javax.xml.bind.annotation.*;
    
    public class Demo {
    
        public static void main(String[] args) {
            try {
                TestRoot root = new TestRoot();
                root.name.value = "bobby";
                root.age.value = 102;
                root.color.value = "blue";
    
                JAXBContext context = JAXBContext.newInstance(root.getClass());
                Marshaller marsh = context.createMarshaller();
                marsh.setProperty(Marshaller.JAXB_ENCODING,"UTF-8");
                marsh.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT,Boolean.TRUE);
    
                StringWriter sw = new StringWriter();
                PrintWriter pw = new PrintWriter(sw);
                marsh.marshal(root,pw);
                System.out.println(sw.toString());
            }
            catch(Throwable t) {
                t.printStackTrace();
            }
        }
    
        @XmlRootElement
        static class TestRoot {
            @XmlElement public TestString name = new TestString(true);
            @XmlElement public TestInteger age = new TestInteger(true);
            @XmlElement public TestString color = new TestString(true);
        }
    
        @XmlAccessorType(XmlAccessType.NONE)
        @XmlTransient
        @XmlSeeAlso({TestInteger.class, TestString.class})
        static class TestGeneric<T> {
            @XmlAttribute 
            public boolean isRequired;
            public T value;
    
            public TestGeneric() {
            }
    
            public TestGeneric(boolean isRequired) {
                this.isRequired = isRequired;
            }
        }
    
        static class TestInteger extends TestGeneric<Integer> {
            public TestInteger() {
            }
            public TestInteger(boolean b) {
                super(b);
            }
            @XmlElement
            public Integer getValue() {
                return value;
            }
            public void setValue(Integer value) {
                this.value = value;
            }
        }
    
        static class TestString extends TestGeneric<String> {
            public TestString() {
            }
            public TestString(boolean b) {
                super(b);
            }
            @XmlElement
            public String getValue() {
                return value;
            }
            public void setValue(String value) {
                this.value = value;
            }
        }
    
    }