Search code examples
javaxmljaxbunmarshallingjaxb2

Jax-b is interpreting too much while unmarshalling


I have a boolean field called a and two methods void setA(String a) and boolean isA(). I have set @XmlAccessorType(XmlAccessType.NONE) and used @XmlAttribute for the setter.

Because the getter returns a boolean value but the setter expects a string JAX-B just ignores this setter. This is the cause for all kinds of bugs in the code because boolean values are not set correctly and debugging that is very annoying.

Is there a way to tell JAX-B to use the setter? Why is JAX-B confused by the getter method at all, I though using XmlAccessType.NONE prevents all that implicit interpreting?

Plan B would be to let JAX-B fails if such a constellation appears, but how can this be done?

Thankful for any hint :-)


Solution

  • I would recommend using @XmlAccessType.FIELD as suggested by Kevin combined with an XmlAdapter to get the behaviour you are looking for:

    Root

    To get this example to work with the JAXB-RI I need to make the field of type Boolean. If you are using EclipseLink JAXB (MOXy) then you can make the field boolean.

    package forum7876493;
    
    import javax.xml.bind.annotation.XmlAttribute;
    import javax.xml.bind.annotation.XmlRootElement;
    import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter;
    
    @XmlRootElement
    public class Root {
    
        @XmlAttribute
        @XmlJavaTypeAdapter(BooleanAdapter.class)
        private Boolean a;
    
        public boolean isA() {
            return a;
        }
    
        public void setA(String s) {
            this.a = "yes".equals(s) || "on".equals(s) || "in".equals(s);
        }
    
    }
    

    BooleanAdapter

    The XmlAdapter is where you can add the logic that you have in your setA(String) method.

    package forum7876493;
    
    import javax.xml.bind.annotation.adapters.XmlAdapter;
    
    public class BooleanAdapter extends XmlAdapter<String, Boolean> {
    
        @Override
        public Boolean unmarshal(String s) throws Exception {
            return "yes".equals(s) || "on".equals(s) || "in".equals(s);
        }
    
        @Override
        public String marshal(Boolean b) throws Exception {
            if(b) {
                return "yes";
            }
            return "no";
        }
    
    }
    

    Demo

    package forum7876493;
    
    import java.io.File;
    
    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 jc = JAXBContext.newInstance(Root.class);
    
            Unmarshaller unmarshaller = jc.createUnmarshaller();
            Root root = (Root) unmarshaller.unmarshal(new File("src/forum7876493/input.xml"));
    
            Marshaller marshaller = jc.createMarshaller();
            marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true);
            marshaller.marshal(root, System.out);
        }
    
    }
    

    input.xml

    <?xml version="1.0" encoding="UTF-8"?>
    <root a="on"/>
    

    Output

    <?xml version="1.0" encoding="UTF-8" standalone="yes"?>
    <root a="yes"/>
    

    UPDATE

    Alternatively you could introduce a String getter for the a property. You would need to make the isA() method as @XmlTransient:

    package forum7876493;
    
    import javax.xml.bind.annotation.XmlAttribute;
    import javax.xml.bind.annotation.XmlRootElement;
    import javax.xml.bind.annotation.XmlTransient;
    
    @XmlRootElement
    public class Root {
    
        private boolean a;
    
        @XmlTransient
        public boolean isA() {
            return a;
        }
    
        @XmlAttribute
        public String getA() {
            if(a) {
                return "yes";
            }
            return "no";
        }
    
        public void setA(String s) {
            this.a = "yes".equals(s) || "on".equals(s) || "in".equals(s);
        }
    
    }