I'm struggling to convert some XML into objects of generic classes. My code works well with non-generic classes, but doesn't with generic ones.
Let's look at this example, it makes it easier to explain. I use both generic and non-generic classes to compare their behaviour.
Xml file:
<?xml version="1.0" encoding="UTF-8"?>
<Box>
<doubleFieldA>
<myGenericField>20.3</myGenericField>
</doubleFieldA>
<doubleFieldB>
<myNonGenericField>20.3</myNonGenericField>
</doubleFieldB>
</Box>
Xsd schema:
<xs:schema version="1.0" xmlns:xs="http://www.w3.org/2001/XMLSchema">
<xs:element name="Box">
<xs:complexType>
<xs:sequence>
<xs:element name="doubleFieldA">
<xs:complexType>
<xs:sequence>
<xs:element type="xs:double" name="myGenericField"/>
</xs:sequence>
</xs:complexType>
</xs:element>
<xs:element name="doubleFieldB">
<xs:complexType>
<xs:sequence>
<xs:element type="xs:double" name="myNonGenericField"/>
</xs:sequence>
</xs:complexType>
</xs:element>
</xs:sequence>
</xs:complexType>
</xs:element>
GenericClass.java:
public class GenericClass<T> {
public T myGenericField;
}
NonGenericClass.java:
public class NonGenericClass {
public double myNonGenericField;
}
Box.java:
import com.thoughtworks.xstream.XStream;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Paths;
public class Box {
public GenericClass<Double> doubleFieldA;
public NonGenericClass doubleFieldB;
private void deserialize() {
Box b;
String s;
XStream xs = new XStream();
try{
s = new String(Files.readAllBytes(Paths.get("src/my_xml.xml")));
b = (Box)xs.fromXML(s);
doubleFieldA = b.doubleFieldA;
doubleFieldB = b.doubleFieldB;
} catch (IOException ioe) {
System.err.println("IOException: " + ioe.getMessage());
}
}
public Box() {
deserialize();
}
}
Main.java:
public class Main {
public static void main(String[] args) {
Box box = new Box();
System.out.println("myGenericField = " + box.doubleFieldA.myGenericField);
System.out.println("myNonGenericField = " + box.doubleFieldB.myNonGenericField);
}
}
Output:
myGenericField = java.lang.Object@2d928643
myNonGenericField = 20.3
Expected output (what I want!):
myGenericField = 20.3
myNonGenericField = 20.3
I have a Box class with two attributes: one is a generic class, the other just a normal class.
Both have an attribute, whose type is double in this particular example.
I try to initialize them with the values stored in the XML file (20.3 for both), using XStream.
Eventually, when I print these attributes, I get the correct value for the non-generic class. The generic class attribute, on the contrary, results in a java.lang.Object.
How should I modify my code to obtain the expected behaviour?
I suspect the issue may be in the way XStream handles generic types, but I'm not sure. I don't have much experience with XStream and XML technologies in general, so I'd really appreciate an answer with working code/examples rather than "you need to implement XYZ, find out how in this messy tutorial!".
Thank you very much!
Note: the only purpose of this code is to show my issue. I encountered this problem in a way larger project, so this is just a small example to reproduce it.
There is no good way to do it with XStream, it just doesn't have any idea that generics even exist. You can add custom converter:
public static class BoxConverter implements Converter {
public boolean canConvert(Class clazz) {
return clazz.equals(Box.class);
}
public void marshal(Object value, HierarchicalStreamWriter writer,
MarshallingContext context) {
throw new RuntimeException("to do");
}
public Object unmarshal(HierarchicalStreamReader reader,
UnmarshallingContext context) {
Box box = new Box();
while (reader.hasMoreChildren()) {
reader.moveDown();
if ("doubleFieldA".equals(reader.getNodeName())) {
reader.moveDown();
Double val = Double.valueOf(reader.getValue());
reader.moveUp();
GenericClass<Double> genericObject = new GenericClass<>();
genericObject.myGenericField = val;
box.doubleFieldA = genericObject;
} else if ("doubleFieldB".equals(reader.getNodeName())) {
box.doubleFieldB =(NonGenericClass)context.convertAnother(box, NonGenericClass.class);
}
reader.moveUp();
}
return box;
}
}
And the register it:
XStream xs = new XStream();
xs.registerConverter(new BoxConverter());
Box b = (Box) xs.fromXML(input);
But that requires to write a separate converter for each class that has GenericClass as a field/member. Notice that when you marshal such object with XStream it generates:
<Box>
<doubleFieldA>
<myGenericField class="double">20.3</myGenericField>
</doubleFieldA>
<doubleFieldB>
<myNonGenericField>20.3</myNonGenericField>
</doubleFieldB>
</Box>
and that additional class="double"
is something that XStream is unable to infer on its own.