I am using XStream. At the moment it is not easy to replace XStream with something else.
I have an interface (MyInterface) and several sub-classes which implement that interface (in the sample code below, there is one called MyImplementation).
I want to serialize and deserialize instances of the sub-classes. I found that I can deserialize just fine if I put the class attribute into the XML:
<myInterfaceElement class="myPackage.MyImplementation">
<field1>value1</field1>
<field2>value2</field2>
</myInterfaceElement>
However, I do not know how to get XStream to write the class attribute. How can I get XStream to include the class attribute when serializing? Or is there another way to serialize/deserialize a class hierarchy so that the element name is the same for all implementations and each subclass can have their own fields defined?
Here is an example of MyInterface, MyImplementation, a JUnit test case trying to make it work. The deserializeWithClassAttribute test passes while the classAttributeSetInResult fails.
package myPackage;
public interface MyInterface {
}
package myPackage;
public class MyImplementation implements MyInterface {
public String field1;
public String field2;
public MyImplementation(String field1, String field2) {
this.field1 = field1;
this.field2 = field2;
}
}
package myPackage;
import org.junit.Test;
import com.thoughtworks.xstream.XStream;
import com.thoughtworks.xstream.io.xml.DomDriver;
import static org.junit.Assert.*;
public class xstreamTest {
@Test
public void classAttributeSetInResult() {
MyInterface objectToSerialize = new MyImplementation("value1", "value2");
final XStream xStream = new XStream(new DomDriver());
xStream.alias("myInterfaceElement", MyInterface.class);
String xmlResult = xStream.toXML(objectToSerialize).toString();
String expectedResult =
"<myInterfaceElement class=\"myPackage.MyImplementation\">\n" +
" <field1>value1</field1>\n" +
" <field2>value2</field2>\n" +
"</myInterfaceElement>";
assertEquals(expectedResult, xmlResult);
}
@Test
public void deserializeWithClassAttribute() {
String inputXmlString =
"<myInterfaceElement class=\"myPackage.MyImplementation\">\r\n" +
" <field1>value1</field1>\r\n" +
" <field2>value2</field2>\r\n" +
"</myInterfaceElement>";
final XStream xStream = new XStream(new DomDriver());
MyInterface result = (MyInterface)xStream.fromXML(inputXmlString);
assertTrue("Instance of MyImplementation returned", result instanceof MyImplementation);
MyImplementation resultAsMyImplementation = (MyImplementation)result;
assertEquals("Field 1 deserialized", "value1", resultAsMyImplementation.field1);
assertEquals("Field 2 deserialized", "value2", resultAsMyImplementation.field2);
}
}
I figured this out by doing the following (thanks to McD on the hint to use a Converter):
Add a custom Converter that extends ReflectionConverter:
package myPackage;
import com.thoughtworks.xstream.converters.MarshallingContext;
import com.thoughtworks.xstream.converters.reflection.ReflectionConverter;
import com.thoughtworks.xstream.converters.reflection.ReflectionProvider;
import com.thoughtworks.xstream.io.HierarchicalStreamWriter;
import com.thoughtworks.xstream.mapper.Mapper;
public class MyInterfaceConverter extends ReflectionConverter {
public MyInterfaceConverter(Mapper mapper, ReflectionProvider reflectionProvider) {
super(mapper, reflectionProvider);
}
@Override
public void marshal(Object original, final HierarchicalStreamWriter writer, final MarshallingContext context) {
writer.addAttribute("class", original.getClass().getCanonicalName());
super.marshal(original, writer, context);
}
@SuppressWarnings("rawtypes")
@Override
public boolean canConvert(Class type) {
return MyInterface.class.isAssignableFrom(type);
}
}
Registering the new Converter when I setup xStream:
@Test
public void classAttributeSetInResult() {
MyInterface objectToSerialize = new MyImplementation("value1", "value2");
final XStream xStream = new XStream(new DomDriver());
xStream.alias("myInterfaceElement", MyImplementation.class);
xStream.registerConverter(new MyInterfaceConverter(xStream.getMapper(), xStream.getReflectionProvider()));
String xmlResult = xStream.toXML(objectToSerialize).toString();
String expectedResult =
"<myInterfaceElement class=\"myPackage.MyImplementation\">\n" +
" <field1>value1</field1>\n" +
" <field2>value2</field2>\n" +
"</myInterfaceElement>";
assertEquals(expectedResult, xmlResult);
}
Hopefully this will help someone else down the road. If anyone has a better idea, please let me know!