Set default value for fields not in XML in XStream

2019-03-26 14:36发布

问题:

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.

回答1:

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.

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 normally 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 detailed - 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.



回答2:

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.



回答3:

c will be set to null when the XML does not have any value set for c. You can then check for null in your getter and return the appropriate default value.