I have XML structure as below:
<Groups>
<Products>
<Product>
<Name>One</Name>
</Product>
<Product>
<Name>Two</Name>
</Product>
</Products>
<OtherProducts>
<Product>
<Id>1</Id>
</Product>
<Product>
<Id>2</Id>
</Product>
</OtherProducts>
</Groups>
I am trying to parse this using XStream, with the following classes:
@XStreamAlias("Groups")
class GroupData {
List<Product> Products;
List<OtherProduct> OtherProducts;
}
@XStreamAlias("Product")
class Product {
String name;
}
@XStreamAlias("Product")
class OtherProduct {
int id;
}
And therein lies the problem -- the parser tries to convert the "Product" items using the "OtherProduct" class.
I believe there must be some way of specifying the class to use to parse an XML object, but I can't make heads or tails of the XStream attributes.
Any help would be greatly appreciated.
The solution is not straighforward.
The marshal (Object to XML) is quite simple, the problem comes when you have to unmarshal (XML to Object) it.
What happens is that when XStream starts reading the XML and it finds a Product tag, it can't know for sure if it is a "Product" or "OtherProduct" object, since the tag name is the same.
So, you have to teach XStream to look ahead and check for something that you know that makes them different. In that case, the inner attributes "Name" and "Id".
You can teach XSTream how to do that writing Converters.
The solution above shows how to solve the problem.
Product class:
@XStreamAlias("Product")
public class Product {
@XStreamAlias("Name")
private String name;
public Product() {
}
public Product(String name) {
this.name = name;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
OtherProduct class:
@XStreamAlias("Product")
public class OtherProduct {
@XStreamAlias("Id")
private int id;
public OtherProduct() {
}
public OtherProduct(int id) {
this.id = id;
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
}
GroupData class:
@XStreamAlias("Groups")
public class GroupData {
@XStreamAlias("Products")
private List<Product> products = new ArrayList<>();
@XStreamAlias("OtherProducts")
private List<OtherProduct> otherProducts = new ArrayList<>();
public void add(Product product) {
getProducts().add(product);
}
public void add(OtherProduct otherProduct) {
getOtherProducts().add(otherProduct);
}
public List<Product> getProducts() {
return products;
}
public List<OtherProduct> getOtherProducts() {
return otherProducts;
}
}
ProductConverter class:
public class ProductConverter implements Converter {
private ProductUnmarshaller productUnmarshaller = new ProductUnmarshaller();
@Override
public boolean canConvert(@SuppressWarnings("rawtypes") Class clazz) {
return clazz.equals(Product.class);
}
@Override
public void marshal(Object object, HierarchicalStreamWriter hsw, MarshallingContext mc) {
Product product = (Product) object;
hsw.startNode("Name");
hsw.setValue(product.getName());
hsw.endNode();
}
@Override
public Object unmarshal(HierarchicalStreamReader hsr, UnmarshallingContext uc) {
return productUnmarshaller.unmarshal(hsr, uc);
}
}
OtherProductConverter class:
public class OtherProductConverter implements Converter {
private ProductUnmarshaller productUnmarshaller = new ProductUnmarshaller();
@Override
public boolean canConvert(@SuppressWarnings("rawtypes") Class clazz) {
return clazz.equals(OtherProduct.class);
}
@Override
public void marshal(Object object, HierarchicalStreamWriter hsw, MarshallingContext mc) {
OtherProduct otherProduct = (OtherProduct) object;
hsw.startNode("Id");
hsw.setValue(Integer.toString(otherProduct.getId()));
hsw.endNode();
}
@Override
public Object unmarshal(HierarchicalStreamReader hsr, UnmarshallingContext uc) {
return productUnmarshaller.unmarshal(hsr, uc);
}
}
ProductUnmarsheller class:
public class ProductUnmarshaller {
public Object unmarshal(HierarchicalStreamReader hsr, UnmarshallingContext uc) {
hsr.moveDown();
String nodeName = hsr.getNodeName();
String nodeValue = hsr.getValue();
hsr.moveUp();
if ("Name".equals(nodeName)) {
return new Product(nodeValue);
} else if ("Id".equals(nodeName)) {
return new OtherProduct(Integer.parseInt(nodeValue));
} else {
return null;
}
}
}
And finally, a class using it all:
public class ProductTest {
@Test
public void test() {
Product productOne = new Product("One");
Product productTwo = new Product("Two");
OtherProduct otherProduct1 = new OtherProduct(1);
OtherProduct otherProduct2 = new OtherProduct(2);
GroupData group = new GroupData();
group.add(productOne);
group.add(productTwo);
group.add(otherProduct1);
group.add(otherProduct2);
XStream xs = new XStream();
xs.processAnnotations(GroupData.class);
xs.processAnnotations(OtherProduct.class);
xs.processAnnotations(Product.class);
xs.registerConverter(new ProductConverter());
xs.registerConverter(new OtherProductConverter());
String xml = xs.toXML(group);
System.out.println(xml);
GroupData gd = (GroupData) xs.fromXML(xml);
for (Product product: gd.getProducts()) {
System.out.println(product.getName());
}
for (OtherProduct otherProduct: gd.getOtherProducts()) {
System.out.println(otherProduct.getId());
}
}
}