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