Search code examples
javaxstream

XStream - Root as a collection of objects


I'm consuming an XML payload that looks something like this (for a more comprehensive example, check out : http://api.shopify.com/product.html ).

<products type="array">
   <product>
      ...
   </product>
   <product>
      ...
   </product>
</products>

Now currently my code does work, but its doing something that appears to be really really "wrong" - namely it associates the "products" with List.class. So the relevant code looks like the following:

    xstream.alias( "products", List.class );
    xstream.alias( "product", ShopifyProduct.class );

This is fine except when I goto externalize any object with that xstream instance it always uses "products" of course, which is not what I want.

I'd like to either be able to map generic collections to a tag:

xstream.alias( "products", ( List<ShopifyProduct> ).class ); // way too easy 

Or get the following snipet to work, which does not at the moment:

    ClassAliasingMapper mapper = new ClassAliasingMapper( xstream.getMapper( ) );
    mapper.addClassAlias( "product", ShopifyProduct.class );
    xstream.registerLocalConverter( ShopifyProductResponse.class, "products", new CollectionConverter( mapper ) );

I created the ShopifyProductResponse class to try and wrap ShopifyProduct, but its not having any of that telling me:

com.thoughtworks.xstream.mapper.CannotResolveClassException: products : products at com.thoughtworks.xstream.mapper.DefaultMapper.realClass(DefaultMapper.java:68) at com.thoughtworks.xstream.mapper.MapperWrapper.realClass(MapperWrapper.java:38)

If I add:

xstream.alias( "products", List.class );

back then it goes away ... so it appears to me that the mapperwrapper is not taking hold here - probably because its looking for a ShopifyProductResponse object and finding a List instead - I really don't know.


Solution

  • If i understand correct, this is what you are looking for. ShoppifyProductResponse.java

    public class ShoppifyProductResponse {
    
    private List<ShoppifyProduct> product;
    
    /**
     * @return the products
     */
    public List<ShoppifyProduct> getProducts() {
        return product;
    }
    
    /**
     * @param products
     *            the products to set
     */
    public void setProducts(List<ShoppifyProduct> products) {
        this.product = products;
    }
    

    }

    And a converter for this. UnMarshalling might look like this.

    public Object unmarshal(HierarchicalStreamReader reader, UnmarshallingContext context) {
        /**
         * Tune the code further..
         */
        ShoppifyProductResponse products = new ShoppifyProductResponse();
        List<ShoppifyProduct> lst = new ArrayList<ShoppifyProduct>();
        while (reader.hasMoreChildren()) {
            reader.moveDown();
            ShoppifyProduct thisProduct = (ShoppifyProduct) context.convertAnother(products,
                    ShoppifyProduct.class);
            lst.add(thisProduct);
            reader.moveUp();
        }
        products.setProducts(lst);
        return products;
    }
    

    And you can register it as,

        XStream stream = new XStream();
        stream.alias("products", ShoppifyProductResponse.class);
        stream.registerConverter(new ShoppifyConverter());
        stream.alias("product", ShoppifyProduct.class);
    

    I have tried this and it works pretty much fine. Give it a try and let me know.