Say I have an abstract class named AbstractItem
that is used as a field in another class. When I use XStream to generate the XML I want the element tag to be based on the concrete implementation of the instance of AbstractItem
.
What I get:
<Test>
<item class="Item1" name="name 1" description="description 1"/>
</Test>
What I want:
<Test>
<Item1 name="name 1" description="description 1"/>
</Test>
I tried setting the alias on the XStream
instance by doing:
stream.alias("Item1", Item1.class);
and also using:
stream.aliasType("Item1", Item1.class);
Neither one of the above worked.
Test.java
import com.thoughtworks.xstream.XStream;
import com.thoughtworks.xstream.annotations.XStreamAlias;
@XStreamAlias("Test")
public class Test {
public AbstractItem item;
public static void main(String[] args){
Test t1 = new Test();
Item1 item1 = new Item1();
item1.name = "name 1";
item1.description = "description 1";
t1.item = item1;
XStream stream = new XStream();
stream.setMode(XStream.NO_REFERENCES);
stream.autodetectAnnotations(true);
stream.alias("Item1", Item1.class);
System.out.println(stream.toXML(t1));
}
}
AbstractItem.java
import com.thoughtworks.xstream.annotations.XStreamAsAttribute;
public abstract class AbstractItem {
@XStreamAsAttribute
public String name;
}
Item1.java
import com.thoughtworks.xstream.annotations.XStreamAsAttribute;
public class Item1 extends AbstractItem {
@XStreamAsAttribute
public String description;
}
stream.registerConverter(
new Converter(){
@Override
public boolean canConvert(Class type) {
if (AbstractItem.class.isAssignableFrom(type)){
return true;
}
return false;
}
@Override
public void marshal(Object source, HierarchicalStreamWriter writer,
MarshallingContext context) {
AbstractItem item = (AbstractItem)source;
if(source instanceof Item1){
writer.startNode("Item1");
writer.addAttribute("description",((Item1)item).description);
} else if(source instanceof Item2){
writer.startNode("Item2");
writer.addAttribute("description", ((Item2)item).description);
} else {
writer.startNode("Item");
}
writer.addAttribute("name", item.name);
writer.endNode();
}
@Override
public Object unmarshal(HierarchicalStreamReader reader,
UnmarshallingContext context) {
// TODO Auto-generated method stub
AbstractItem item = null;
String nodeName = reader.getNodeName();
if (nodeName.equals("Item1")){
item = new Item1();
((Item1)item).description = reader.getAttribute("description");
} else if (nodeName.equals("Item2")){
item = new Item2();
((Item2)item).description = reader.getAttribute("description");
}
item.name = reader.getAttribute("name");
return item;
}
});
The result I get now is:
<Test>
<item class="Item1">
<Item1 description="description 1" name="name 1"/>
</item>
</Test>
I found the that the only way to accomplish this is with a custom converter for the class containing the Object I want to manipulate the element tag for. In the example in the question it would be a custom converter for the Test
classand it would look like:
import com.thoughtworks.xstream.converters.Converter;
import com.thoughtworks.xstream.converters.MarshallingContext;
import com.thoughtworks.xstream.converters.UnmarshallingContext;
import com.thoughtworks.xstream.io.HierarchicalStreamReader;
import com.thoughtworks.xstream.io.HierarchicalStreamWriter;
public class BasicConverter implements Converter {
@Override
public boolean canConvert(Class type) {
return Test.class.isAssignableFrom(type);
}
@Override
public void marshal(Object source, HierarchicalStreamWriter writer,
MarshallingContext context) {
if (((Test) source).item instanceof Item1) {
writer.startNode("Item1");
writer.addAttribute("description", ((Item1)((Test) source).item).description);
} else if (((Test) source).item instanceof Item2) {
writer.startNode("Item2");
writer.addAttribute("description", ((Item2)((Test) source).item).description);
}
writer.addAttribute("name", ((Test) source).item.name);
writer.endNode();
}
@Override
public Object unmarshal(HierarchicalStreamReader reader,
UnmarshallingContext context) {
Test test = new Test();
reader.moveDown();
String nodeName = reader.getNodeName();
AbstractItem item = null;
if (nodeName.equals("Item1")) {
item = new Item1();
((Item1)item).description = reader.getAttribute("description");
} else if (nodeName.equals("Item2")) {
item = new Item2();
((Item2)item).description = reader.getAttribute("description");
}
item.name = reader.getAttribute("name");
((Test)test).item = item;
reader.moveUp();
return test;
}
}
This gives the output I was looking for above, but to me is not really satisfactory. The reason being, is that the actual class I need to use this for has tons of fields, some that use their own custom converters, custom alias, etc. Plus it will essentially disregard all the annotations on the Test class, besides those defined at the class level. Plus as your class grows you have to update this converter to handle those new fields, or they will be included.
Ideally I would like a converter that does everything as defined by annotations, except for certain fields. There currently is not one that I know of. What I am in the process of doing is extending the com.thoughtworks.xstream.converters.reflection.ReflectionConverter
class to accomplish this. But it requires copying more code from the underlying implementation then I particularly care for.