Reputation: 752
I'm trying to convert from XML to an object tree using XStream. I'd like a specific subclass to be created based on an attribute.
How would I go about doing this?
<items>
<item>
<event type="aaa">
<timestamp>2014-04-10 15:58:08 UTC</timestamp>
</event>
<event type="bbb">
<timestamp>2014-04-03 11:58:08 UTC</timestamp>
</event>
</item>
</items>
When I simply use XStream with aliases and ONE Event class, it works fine.
xstream.alias("items", Items.class);
xstream.alias("event", Event.class);
However, I would like XStream to create a different class per Event type. I have classes EventAAA and EventBBB that both extend from abstract Event. How can I tell XStream to take that into account when unmarshalling? XStream currently always tries to instantiate Event and fails because it's abstract.
Cheers!
Upvotes: 1
Views: 1537
Reputation: 1538
Example code to do what you want (and more):
import com.thoughtworks.xstream.XStream;
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;
import java.util.ArrayList;
import java.util.Arrays;
/**
* http://stackoverflow.com/posts/23792750
*/
public class App {
public static class Items {
private ArrayList<Item> e;
public Items(ArrayList<Item> e) {
this.e = e;
}
}
public static class Item {
private ArrayList<Event> e;
public Item(ArrayList<Event> e) {
this.e = e;
}
}
public static void main(String[] args) {
Items items = new Items(
new ArrayList<Item>(
Arrays.asList(
new Item(
new ArrayList(
Arrays.<Event>asList(new EventAAA(), new EventBBB())
)
)
)
)
);
XStream xs = new XStream();
xs.registerConverter(new EventConverter());
xs.registerConverter(new ItemConverter());
xs.alias("item", Item.class);
xs.addImplicitArray(Item.class, "e");
xs.alias("items", Items.class);
xs.addImplicitArray(Items.class, "e");
System.out.println("Serialize individual event objects:");
System.out.println(xs.toXML(new EventAAA()));
System.out.println(xs.toXML(new EventBBB()));
System.out.println("De-serialize individual event objects:");
System.out.println(xs.fromXML(xs.toXML(new EventAAA())).toString());
System.out.println(xs.fromXML(xs.toXML(new EventAAA())).getClass().getName());
System.out.println(xs.fromXML(xs.toXML(new EventBBB())).toString());
System.out.println(xs.fromXML(xs.toXML(new EventBBB())).getClass().getName());
System.out.println("Show serialization of ArrayList<Item> items:");
System.out.println(xs.toXML(items));
System.out.println("Show de-serialization of ArrayList<Item> items:");
System.out.println(xs.fromXML(xs.toXML(items)));
System.out.println("Show correct type information in de-serialization for elements in e:");
Items items2 = (Items) xs.fromXML(xs.toXML(items));
for (Item i : items2.e) {
for (Event e : i.e) {
System.out.println(e.getClass().getName());
}
}
}
public static class Timestamp {
public Timestamp(String timestamp) {
}
}
public static abstract class Event {
public abstract String getTypeName();
private Timestamp timestamp = new Timestamp("");
public void setTimestamp(Timestamp t) {
this.timestamp = t;
}
public Timestamp getTimestamp() {
return timestamp;
}
}
public static class EventAAA extends Event {
@Override
public String getTypeName() {
return "aaa";
}
}
public static class EventBBB extends Event {
@Override
public String getTypeName() {
return "bbb";
}
}
public static class ItemConverter implements Converter {
@Override
public void marshal(Object o, HierarchicalStreamWriter writer, MarshallingContext mc) {
Item i = (Item) o;
for (Event e : i.e) {
writer.startNode("event");
mc.convertAnother(e);
writer.endNode();
}
}
@Override
public Object unmarshal(HierarchicalStreamReader reader, UnmarshallingContext uc) {
Item i = new Item(new ArrayList<>());
while (reader.hasMoreChildren()) {
i.e.add((Event) uc.convertAnother(i, Event.class));
}
return i;
}
@Override
public boolean canConvert(Class type) {
return (type.equals(Item.class));
}
}
public static class EventConverter implements Converter {
public boolean canConvert(Class clazz) {
return Event.class.isAssignableFrom(clazz);
}
public void marshal(Object value, HierarchicalStreamWriter writer,
MarshallingContext context) {
Event e = (Event) value;
writer.addAttribute("type", e.getTypeName());
writer.startNode("timestamp");
writer.setValue(e.getTimestamp().toString());
writer.endNode();
}
public Object unmarshal(HierarchicalStreamReader reader,
UnmarshallingContext context) {
String type = reader.getAttribute("type");
Event e;
if (type.equals("aaa")) {
e = new EventAAA();
} else if (type.equals("bbb")) {
e = new EventBBB();
} else {
throw new IllegalArgumentException("Encountered illegal type of event: " + type);
}
reader.moveDown();
e.setTimestamp(new Timestamp(reader.getValue()));
reader.moveUp();
return e;
}
}
}
Output from example code:
Serialize individual event objects:
<App_-EventAAA type="aaa">
<timestamp>App$Timestamp@184cf7cf</timestamp>
</App_-EventAAA>
<App_-EventBBB type="bbb">
<timestamp>App$Timestamp@5bfa9431</timestamp>
</App_-EventBBB>
De-serialize individual event objects:
App$EventAAA@48fa0f47
App$EventAAA
App$EventBBB@161479c6
App$EventBBB
Show serialization of ArrayList<Item> items:
<items>
<item>
<event type="aaa">
<timestamp>App$Timestamp@5c909414</timestamp>
</event>
<event type="bbb">
<timestamp>App$Timestamp@65466a6a</timestamp>
</event>
</item>
</items>
Show de-serialization of ArrayList<Item> items:
App$Items@3eb7fc54
Show correct type information in de-serialization for elements in e:
App$EventAAA
App$EventBBB
As you can see, EventAAA
and EventBBB
got serialized into <event>
nodes with appropriate type
attributes, but the <event>
got de-serialized back into the right object types (App$EventAAA
and App$EventBBB
, last two lines of output, "App$" prefix coming from use of inner classes).
You should be able to use this almost as-is for your code; you will need to replace the stub classes with your real classes.
Upvotes: 2