Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Set default value for fields not in XML in XStream

Is there a way to create a converter or some operation that is performed after every single conversion? For context, I am trying to populate default values for fields that are not in my XML in order to maintain backwards compatibility if my data model changes. For instance, if I had this object:

class A {
    private String b;
    private String c;
    private String d;
}

and my XML was something like:

<a>
 <b>b</b>
 <d>d</d>
</a>

I want my import of the XML to know that there is a default value for the field c that is "c" and set it on A as such. This should be a generic operation to which I can add defaults to any field of a very complex graph. If there were some way to trigger a function after every conversion, it could check the current object against a map of objects I'd like to set a default value on.

Also note, that using readResolve/readObject does not seem to be an option since 1. readObject() never seemed to work for me at all and 2. readResolve would overwrite the field with the default value even if it were actually included in the XML. Please let me know if my assumptions here are wrong though.

Edit:: I found this related thread on the user mailing list: http://article.gmane.org/gmane.comp.java.xstream.user/4619/match=default+value

and it seems like the only suggested solution is to use readResolve() which I already said was not a valid solution.

like image 363
eipark Avatar asked Apr 17 '13 13:04

eipark


People also ask

How do you set a default field value in Java?

To set a default value for primitive types such as boolean and int, we use the literal value: @Value("${some.


2 Answers

Use the PureJavaReflectionProvider

XStream xstream = new XStream(new PureJavaReflectionProvider());

and just initialize your object with default values as usual. Either through field initialization or constructor code (initializer).

background

If you do not specify a ReflectionProvider xstream tries to find the best reflection provider. But the best ReflectionProvider for xstream might not be the best for you, because it usually selects the Sun14ReflectionProvider.

The Sun14ReflectionProvider uses the same instantiation strategy as the java serialization mechanism and that means that it skips constructor code or to be more precise - the object initializer code.

Therefore instance field initializations like

class A {
    private String b = "DEFAULT";
}

will NOT be applied and also constructor code will NOT be applied, e.g.

class A {
    private String b;

    public A(){
        b = "DEFAULT";
    }
}

The PureJavaReflectionProvider instead uses (as the name implies) the java reflection API to instantiate objects, e.g. Class.newInstance() and therefore object initialization code is executed.

like image 68
René Link Avatar answered Oct 25 '22 12:10

René Link


You'll need a converter.

Here is a code example for your case:

public class AConverter implements Converter {

    @Override
public Object unmarshal(HierarchicalStreamReader reader, UnmarshallingContext context) {
    A a = new A();
    String bValue = "b";
    String cValue = "c";
    String dValue = "d";

    while (reader.hasMoreChildren()) {
        reader.moveDown();
        if ("b".equals(reader.getNodeName())) {
            bValue = reader.getValue();
        } else if ("c".equals(reader.getNodeName())) {
            cValue = reader.getValue();
        } else if ("d".equals(reader.getNodeName())) {
            dValue = reader.getValue();
        }
        reader.moveUp();
    }
    a.setB(bValue);
    a.setC(cValue);
    a.setD(dValue);

    return a;
}

@Override
public void marshal(Object object, HierarchicalStreamWriter writer, MarshallingContext context) {
    A a = (A) object;
    writer.startNode("b");
    writer.setValue(a.getB());
    writer.endNode();
    writer.startNode("c");
    writer.setValue(a.getC());
    writer.endNode();
    writer.startNode("d");
    writer.setValue(a.getD());
    writer.endNode();


}

@Override
public boolean canConvert(Class clazz) {
    return clazz == A.class;
}

}

Do not forget to register the converter:

XStream xs = new XStream();
xs.registerConverter(new AConverter());

EDIT: Fixed the converter code.

like image 33
pablosaraiva Avatar answered Oct 25 '22 12:10

pablosaraiva