Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Prevent writing default attribute values JAXB

Tags:

java

jaxb

I am trying to write the class in order to write an element with attributes in JAXB. In this XML there are some default values whether they be Strings, ints, or custom class types.

The following cut down example:

@XmlAccessorType(XmlAccessType.NONE)
@XmlRootElement(name = "FIELD")
public class TestLayoutNode
{
    // I want to not write this to the xml when it is 0
    @XmlAttribute(name = "num")
    private int number;

    // I want to not write this when it is "default"
    @XmlAttribute(name = "str")
    private String str;
}

As per JAXB Avoid saving default values I know if I want to not write the String I can modify the getters/setters to write null and read in the default value if it reads in null.

However, with the int I am not sure what to do as it will always have the value 0 unless it is specifically changed.

Is there a nicer way to do this? I could change the internal data types to String and then cast it whenever it is needed but that's a bit messy.

like image 777
Wolsie Avatar asked Feb 05 '15 09:02

Wolsie


2 Answers

You could do the following by changing the fields to be the object types by default null values do not appear in the XML representation) and putting some logic in the getters:

@XmlAccessorType(XmlAccessType.NONE)
@XmlRootElement(name = "FIELD")
public class TestLayoutNode
{

    @XmlAttribute(name = "num")
    private Integer number;

    @XmlAttribute
    private String str;

    public int getNumber() {
        if(null == number) {
           return 0;
        } else {
           return number;
        }
    }

    public void setNumber(int number) {
        this.number = number;
    }

    public String getStr() {
        if(null == str) {
            return "default";
        } else {
            return str;
        }
    }

    public void setStr(String str) {
        this.str = str;
    }
}

Allowing the Property to be Unset

If you want to allow the set operation to return a property to its default state then you need to add logic in the set method.

public void setNumber(int number) {
    if(0 == number) {
        this.number = null;
    } else {
        this.number = number;
    }
 }

Alternatively you could offer an unset method:

public void unsetNumber() {
    this.number = null;
}

Allowing a Set to null

If you want to allow the str property to be set to null so that the get method will return null and not "default" then you can maintain a flag to track if it has been set:

    private strSet = false;

    public String getStr() {
        if(null == str && !strSet) {
            return "default";
        } else {
            return str;
        }
    }

    public void setStr(String str) {
        this.str = str;
        this.strSet = true;
    }

UPDATE

Blaise, don't you think that the solution is pretty verbose?

Yes

I mean that such use case should be probably supported by framework. For example using annotation like @DefaultValue.

How JAXB Supports Default Values Today

If a node is absent from the XML then a set is not performed on the corresponding field/property in the Java Object. This means whatever value you have initialized the property to be is still there. On a marshal since the value is populated it will be marshalled out.

What is Really Being Asked For

What is really being asked for is to not marshal the field/property when it has the default value. In this way you want the marshal behaviour to be the same for null and default values. This introduces some problems to be solved:

  1. How do you now marshal null to XML? By default is it still marshalled as a missing node?
  2. Does a mechanism need to be provided to distinguish between the property being the default value (not present in the XML) and having been set to the same value as the default (present in the XML)?

What Are People Doing Today?

Generally for this use case people would just change the int property to Integer and have null be the default. I haven't encountered someone asking for this behaviour for a String before.

like image 123
bdoughan Avatar answered Oct 17 '22 11:10

bdoughan


Use Integer instead of primitive int. Replace all primitive types with their object counterparts, then you can use NULL.

As per the string default value, use and modify the getter

import javax.xml.bind.JAXBContext;
import javax.xml.bind.JAXBException;
import javax.xml.bind.Marshaller;
import javax.xml.bind.annotation.XmlAttribute;
import javax.xml.bind.annotation.XmlElement;
import javax.xml.bind.annotation.XmlRootElement;

@XmlRootElement(name = "FIELD")
public class NullAttrs {

    private Integer number;
    private String str;

    public void setNumber(Integer number) {
        this.number = number;
    }

    @XmlAttribute(name = "num")
    public Integer getNumber() {
        return number;
    }

    public void setStr(String str) {
        this.str = str;
    }

    @XmlAttribute(name = "str")
    public String getStr() {
        if (str != null && str.equalsIgnoreCase("default"))
         return null;
        else if (str == null)
         return "default";
        else
         return str;
    }

    public static void main(String[] args) throws JAXBException {
        JAXBContext jc = JAXBContext.newInstance(NullAttrs.class);

        NullAttrs root = new NullAttrs();
        root.setNumber(null);
        root.setStr("default");

        Marshaller marshaller = jc.createMarshaller();
        marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true);
        marshaller.marshal(root, System.out);
    }
}

Result in this case would be, empty FIELD:

<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<FIELD/>
like image 35
MihaiC Avatar answered Oct 17 '22 13:10

MihaiC