XStream - Parsing Nested XML Element (skipping parent element)

1,269 views
Skip to first unread message

Bruno Riev

unread,
Aug 18, 2015, 10:22:11 PM8/18/15
to XStream User
Hello all.

I'm facing pretty unusual (I guess ?) scenario where I'm having a hard time trying to parse an XML file w/ XStream, I'd like to ask your help for enlighment.

Here's the deal:

I have the following XML that I'm trying to parse w/ XStream. Everything works fine up to the point where I reach "<Keywords>...</Keywords>" element.

I'd like to get all of the <KEYWORD> elements (keyword_1, keyword_2, keyword_3, ..., keyword_n), without having to make a class to handle the <KEYWORDS>. (This is not an academic exercise, indeed I have a file just like that, but of course, filled with real world data instead of tests)...

================================
Sample.XML
================================

<CATALOG>
<PRODUCTS>
<PRODUCT>
<PRODUCTOID>123</PRODUTOID>
<PRODUCTNAME>TEST NAME</NOMEPRODUTO>
<DESCRIPTION>TEST DESCRIPTION</DESCRIPTION>
<KEYWORDS>
<KEYWORD>keyword_1</KEYWORD>
<KEYWORD>keyword_2</KEYWORD>
<KEYWORD>keyword_3</KEYWORD>
<KEYWORD>keyword_n</KEYWORD>
<KEYWORDS>
</PRODUCT>
</PRODUCTS>
</CATALOG>


================================
Catalog.java
================================

@XStreamAlias("CATALOG")
public class Catalog {

@XStreamAlias("PRODUCTS")
private List<Product> products;

// Getters and Setters

}


================================
Products.java
================================

@XStreamAlias("PRODUCT")
public class Product {

@XStreamAlias("PRODUCTID")
private String id;

@XStreamAlias("PRODUCTNAME")
private String name;

@XStreamAlias("DESCRIPTION")
private String description;


// @XStreamImplicit(itemFieldName = "KEYWORD")
// the above line works, *IF* I delete the parent node `<KEYWORDS>`,
// but that's not the case, I have to deal with it

// @StreamAlias("KEYWORDS/KEYWORD")
// this doesn't work

@StreamAlias("KEYWORDS")
// and this returns the error I'm posting below after this class
private List<String> keywords;

// Getters and Setters

}


================================
XStreamTest.java
================================

public class XStreamTest {

public static void main(String[] args) {

String filepath = "Sample.xml";

try {
FileReader reader = new FileReader(filepath);
XStream xstream = new XStream();

xstream.processAnnotations(Catalog.class);
xstream.processAnnotations(Product.class);

Catalog catalog = (Catalog) xstream.fromXML(reader);
System.out.println(catalog.getProducts().get(0).getKeywords().get(0));
}

catch (FileNotFoundException e) {
e.printStackTrace();
}

}


With the current setup, this is what I'm getting (down below is the stack trace). I'm aware why I'm getting this, it's because I'm not mapping the element <KEYWORD> anywhere... but I don't know how to fix it.

And I really did NOT want to make another class, say "Keyword.java", just to have to deal with that <KEYWORDS> tag (like I did with the Product.java, to manage the elements within the <PRODUCT> element - generating a **List<Product> products** in Catalog.java).


Is there anyway I can solve this (without having to make another class) ?
(Maybe writing a custom unmarshaller, no idea of how, I'm just guessing)

Please enlighten me.

Thank you very much in advance.

Jörg Schaible

unread,
Aug 19, 2015, 5:36:52 AM8/19/15
to xstrea...@googlegroups.com
Hi Bruno,

Bruno Riev wrote:

> Hello all.
>
> I'm facing pretty unusual (I guess ?) scenario where I'm having a hard
> time trying to parse an XML file w/ XStream, I'd like to ask your help for
> enlighment.
>
> Here's the deal:
>
> I have the following XML that I'm trying to parse w/ XStream. Everything
> works fine up to the point where I reach "<Keywords>...</Keywords>"
> element.
>
> I'd like to get all of the <KEYWORD> elements (keyword_1, keyword_2,
> keyword_3, ..., keyword_n), without having to make a class to handle the
> <KEYWORDS>.

Well, there's no way around this ... but read on. ;-)
Yes, you will have to use a custom converter, but no, you don't have to
write it for yourself - XStream already delivers one: The
NamedCollectionConverter. Unfortunately that example in http://x-stream.github.io/converters.html#java.util is a copy & paste error.
See the unit tests for better examples: https://github.com/x-stream/xstream/blob/master/xstream/src/test/com/thoughtworks/acceptance/NamedLocalElementsTest.java

You may even register this converter with an annotation. It is a bit tricky
though, but https://github.com/x-stream/xstream/blob/master/xstream/src/test/com/thoughtworks/acceptance/annotations/ParametrizedConverterTest.java
contains a test registering the NamedMapConverter. Look at the javadoc of
the XStreamConverter annotation to adapt the registration for a
NamedCollectionConverter.

Hope this helps,
Jörg


Message has been deleted

Bruno Riev

unread,
Aug 19, 2015, 10:03:52 AM8/19/15
to XStream User, joerg.s...@swisspost.com
Hello Jörg,

Thank you for the reply!

I tried looking into the unit tests you wrote and also to this other thread:
http://xstream.10960.n7.nabble.com/Set-both-collection-name-and-item-name-td8722.html

But it seems I'm also struggling to get NamedCollectionConverter to work properly. I'm trying the approach with Annotation as you instructed that other user. I mean... I added the following to my Product class:


================================
Products.java
================================

@XStreamAlias("PRODUCT")
public class Product {

@XStreamAlias("PRODUCTID")
private String id;

@XStreamAlias("PRODUCTNAME")
private String name;

@XStreamAlias("DESCRIPTION")
private String description;

@XStreamAlias("KEYWORDS")
@XStreamConverter(value= NamedCollectionConverter.class, strings={"KEYWORD"}, types={String.class})
private List<String> keywords;

// Getters and Setters

}


But that results in:


Exception in thread "main" com.thoughtworks.xstream.converters.ConversionException: Explicit selected converter cannot handle type
---- Debugging information ----
item-type : java.lang.String
converter-type : com.thoughtworks.xstream.converters.extended.NamedCollectionConverter
class : obscured-package-name.model.Product
required-type : obscured-package-name.model.Product
converter-type[1] : com.thoughtworks.xstream.converters.reflection.ReflectionConverter
path : /CATALOG/PRODUCTS/PRODUCT/KEYWORDS
line number : 10
class[1] : java.util.ArrayList
converter-type[2] : com.thoughtworks.xstream.converters.collections.CollectionConverter
class[2] : obscured-package-name..model.Catalog
version : 1.4.8
-------------------------------
at com.thoughtworks.xstream.core.TreeUnmarshaller.convertAnother(TreeUnmarshaller.java:59)
(...)


Like, it cannot handle the type String.class ? I don't get it...

I'm trying to get to know it better. I really am, but right now i'm shooting in the dark... what am I missing ? :(

(am in the right path at all ?)

Thank you!

Jörg Schaible

unread,
Aug 19, 2015, 10:14:37 AM8/19/15
to xstrea...@googlegroups.com
Yes, basically it looks right. It complains about the Product class -
strange?!? What annotations do you have for Catalog?

Can you go the other way round? Create an instance of the Catalog, fill it
up a bit and generate the XML. This helps typically to identify where the
XML differs.

Cheers,
Jörg

Bruno Riev

unread,
Aug 19, 2015, 2:42:05 PM8/19/15
to XStream User, joerg.s...@swisspost.com
Sure. Sorry for the delay... Working on it right now.

Bruno Riev

unread,
Aug 19, 2015, 5:19:17 PM8/19/15
to XStream User, joerg.s...@swisspost.com
Jorg, I think we're getting to the root cause of this...

Check this out:

================================
Catalog.java
================================
@XStreamAlias("CATALOG")
public class Catalog {

@XStreamAlias("PRODUCTS")
private List<Product> products;

public Catalog() {
this.products = new ArrayList<Product>();
}

public List<Product> getProducts() {
return this.products;
}
}

================================
Product.java
================================

@XStreamAlias("PRODUCT")
public class Product {

@XStreamAlias("PRODUCTID")
private String id;

@XStreamAlias("PRODUCTNAME")
private String name;

@XStreamAlias("DESCRIPTION")
private String description;

@XStreamAlias("KEYWORDS")
@XStreamConverter(value= NamedCollectionConverter.class, strings={"KEYWORD"}, types={String.class})
private List<String> keywords;


public Product() {
this.keywords = new ArrayList<String>();
}

public String getId() {
return this.id;
}

public void setId(String id) {
this.id = id;
}

public String getName() {
return this.name;
}

public void setName(String name) {
this.name = name;
}

public String getDescription() {
return this.description;
}

public void setDescription(String description) {
this.description = description;
}

public List<String> getKeywords() {
return this.keywords;
}

public void setKeywords(List<String> keywords) {
this.keywords = keywords;
}

}

================================
XStreamTest.java
================================

public class XStreamTest {

public static void main(String[] args) {

String filepath = "Sample.xml";

try {
/*
FileReader reader = new FileReader(filepath);
XStream xstream = new XStream();

xstream.processAnnotations(Catalog.class);
xstream.processAnnotations(Product.class);
xstream.processAnnotations(SKU.class);
xstream.processAnnotations(Store.class);
xstream.processAnnotations(Department.class);
Catalog catalog = (Catalog) xstream.fromXML(reader);
System.out.println(catalog.getProducts().get(0).getDescription());
*/

// Bean to XML
XStream xstream2 = new XStream();

Catalog catalog = new Catalog();

Product product = new Product();
product.setId("123");
product.setName("Test Name");
product.setDescription("Test Description");
product.getKeywords().add("keyword_1");
product.getKeywords().add("keyword_2");
product.getKeywords().add("keyword_3");

catalog.getProducts().add(product);

String XML = xstream2.toXML(catalog);
System.out.println(XML);
}

catch (Exception e) {
e.printStackTrace();
}

}
}

---------------------------------------------------------------------------
***************************** WHICH PRODUCES *****************************
---------------------------------------------------------------------------

<OBSCURED_PACKAGE_ROOT.model.Catalog>
<products>
<OBSCURED_PACKAGE_ROOT.model.Product>
<id>123</id>
<name>Test Name</name>
<description>Test Description</description>
<keywords>
<string>keyword_1</string>
<string>keyword_2</string>
<string>keyword_3</string>
</keywords>
</OBSCURED_PACKAGE_ROOT.model.Product>
</products>
</OBSCURED_PACKAGE_ROOT.model.Catalog>


That is... the XML it generates is a list of <string>keyword_*</string>, which is not wrong... but the original XML I'm parsing is:


<CATALOG>
<PRODUCTS>
<PRODUCT>
<PRODUCTID>123</PRODUTOID>
<PRODUCTNAME>Test Name</PRODUCTNAME>
<DESCRIPTION>Test Description</DESCRIPTION>
<KEYWORDS>
<KEYWORD>keyword_1</KEYWORD>
<KEYWORD>keyword_2</KEYWORD>
</KEYWORDS>
</PRODUCT>
</PRODUCTS>
</CATALOG>


** Notice that the list (keyword_1, keyword_2, ..., keyword_n) is a List of <Keyword> not a List<String>**

So, in order to generate the XML "correctly" (with a list of <Keyword), I'd have to do the following:

1. Make a class "Keyword.java"
2. In "Product.java", have a list like this: "List<Keyword> keywords"

(which is what I'm trying to avoid, originally, you see? (Avoiding making a "Keyword" class. I'd like to have that list "keyword_1", "keyword_2" into that
"List<String> keywords" that is in the Product.java).

Did it make it clear ? (I know it can be confusing)...

Jörg Schaible

unread,
Aug 20, 2015, 3:50:14 AM8/20/15
to xstrea...@googlegroups.com
You did not enable the annotations for the xstream2 instance. Simply use the
first initialized xstream instance for the serialization. The produced XML
should be a lot better then.

Cheers,
Jörg

Bruno Riev

unread,
Aug 20, 2015, 9:03:48 AM8/20/15
to XStream User, joerg.s...@swisspost.com
Oops, duh me...

I managed to get this, Jörg:

<CATALOG>
<PRODUCTS>
<PRODUCT>
<PRODUCTID>123</PRODUCTID>
<PRODUCTNAME>Test Name</PRODUCTNAME>
<DESCRIPTION>Test Description</DESCRIPTION>
<KEYWORDS>
<string>keyword_1</string>
<string>keyword_2</string>
<string>keyword_3</string>
</KEYWORDS>
</PRODUCT>
</PRODUCTS>
</CATALOG>


having Product.java as this:

==============================
PRODUCT.java
=============================

@XStreamAlias("PRODUCT")
public class Product {

@XStreamAlias("PRODUCTID")
private String id;

@XStreamAlias("PRODUCTNAME")
private String name;

@XStreamAlias("DESCRIPTION")
private String description;

@XStreamAlias("KEYWORDS")
// @XStreamConverter(value= NamedCollectionConverter.class, strings={"KEYWORD"}, types={String.class})
private List<String> keywords;

---

If, however, my Product class is like this (@XStreamAlias + @XStreamConverter):

@XStreamAlias("PRODUCT")
public class Product {

@XStreamAlias("PRODUCTID")
private String id;

@XStreamAlias("PRODUCTNAME")
private String name;

@XStreamAlias("DESCRIPTION")
private String description;

@XStreamAlias("KEYWORDS")
@XStreamConverter(value= NamedCollectionConverter.class, strings={"KEYWORD"}, types={String.class})
private List<String> keywords;


--

Then this's the error stack trace what I get :


com.thoughtworks.xstream.converters.ConversionException: Explicit selected converter cannot handle item
---- Debugging information ----
item-type : java.util.ArrayList
converter-type : com.thoughtworks.xstream.converters.extended.NamedCollectionConverter
-------------------------------
at com.thoughtworks.xstream.core.TreeMarshaller.convertAnother(TreeMarshaller.java:51)
at com.thoughtworks.xstream.core.AbstractReferenceMarshaller$1.convertAnother(AbstractReferenceMarshaller.java:84)
at com.thoughtworks.xstream.converters.reflection.AbstractReflectionConverter.marshallField(AbstractReflectionConverter.java:256)
at com.thoughtworks.xstream.converters.reflection.AbstractReflectionConverter$2.writeField(AbstractReflectionConverter.java:232)
at com.thoughtworks.xstream.converters.reflection.AbstractReflectionConverter$2.<init>(AbstractReflectionConverter.java:195)
at com.thoughtworks.xstream.converters.reflection.AbstractReflectionConverter.doMarshal(AbstractReflectionConverter.java:141)
at com.thoughtworks.xstream.converters.reflection.AbstractReflectionConverter.marshal(AbstractReflectionConverter.java:89)
at com.thoughtworks.xstream.core.AbstractReferenceMarshaller.convert(AbstractReferenceMarshaller.java:69)
at com.thoughtworks.xstream.core.TreeMarshaller.convertAnother(TreeMarshaller.java:58)
at com.thoughtworks.xstream.core.TreeMarshaller.convertAnother(TreeMarshaller.java:43)
at com.thoughtworks.xstream.core.AbstractReferenceMarshaller$1.convertAnother(AbstractReferenceMarshaller.java:88)
at com.thoughtworks.xstream.converters.collections.AbstractCollectionConverter.writeItem(AbstractCollectionConverter.java:64)
at com.thoughtworks.xstream.converters.collections.CollectionConverter.marshal(CollectionConverter.java:74)
at com.thoughtworks.xstream.core.AbstractReferenceMarshaller.convert(AbstractReferenceMarshaller.java:69)
at com.thoughtworks.xstream.core.TreeMarshaller.convertAnother(TreeMarshaller.java:58)
at com.thoughtworks.xstream.core.AbstractReferenceMarshaller$1.convertAnother(AbstractReferenceMarshaller.java:84)
at com.thoughtworks.xstream.converters.reflection.AbstractReflectionConverter.marshallField(AbstractReflectionConverter.java:256)
at com.thoughtworks.xstream.converters.reflection.AbstractReflectionConverter$2.writeField(AbstractReflectionConverter.java:232)
at com.thoughtworks.xstream.converters.reflection.AbstractReflectionConverter$2.<init>(AbstractReflectionConverter.java:195)
at com.thoughtworks.xstream.converters.reflection.AbstractReflectionConverter.doMarshal(AbstractReflectionConverter.java:141)
at com.thoughtworks.xstream.converters.reflection.AbstractReflectionConverter.marshal(AbstractReflectionConverter.java:89)
at com.thoughtworks.xstream.core.AbstractReferenceMarshaller.convert(AbstractReferenceMarshaller.java:69)
at com.thoughtworks.xstream.core.TreeMarshaller.convertAnother(TreeMarshaller.java:58)
at com.thoughtworks.xstream.core.TreeMarshaller.convertAnother(TreeMarshaller.java:43)
at com.thoughtworks.xstream.core.TreeMarshaller.start(TreeMarshaller.java:82)
at com.thoughtworks.xstream.core.AbstractTreeMarshallingStrategy.marshal(AbstractTreeMarshallingStrategy.java:37)
at com.thoughtworks.xstream.XStream.marshal(XStream.java:1043)
at com.thoughtworks.xstream.XStream.marshal(XStream.java:1032)
at com.thoughtworks.xstream.XStream.toXML(XStream.java:1005)
at com.thoughtworks.xstream.XStream.toXML(XStream.java:992)


==========================
Main Class:
==========================

public class App {

public static void main(String[] args) {

String filepath = "Sample.xml";

try {

// Bean to XML
XStream xstream2 = new XStream();
xstream2.processAnnotations(Catalog.class);
xstream2.processAnnotations(Product.class);

Catalog catalog = new Catalog();
Product product = new Product();

product.setId("123");
product.setName("Test Name");
product.setDescription("Test Description");
product.getKeywords().add("keyword_1");
product.getKeywords().add("keyword_2");
product.getKeywords().add("keyword_3");

catalog.getProducts().add(product);

String XML = xstream2.toXML(catalog);
System.out.println(XML);
}
catch(Exception e) {
e.printStackTrace();
}

}
}


By what i have seen, the issue I'm having is on that line:

@XStreamConverter(value= NamedCollectionConverter.class, strings={"KEYWORD"}, types={String.class})

in Product.java, right ? It's not getting it right that I want to have "<KEYWORD>" instead of "<string>".


Please, may I ask you for an advice to fix that ?

Thank you!

Best regards,
- Bruno

Jörg Schaible

unread,
Aug 20, 2015, 9:28:17 AM8/20/15
to xstrea...@googlegroups.com
[snip]
It's a bit strange. I would have expected that it works. Can you register
the local converter manually instead of using the annotation?

xstream2.registerlocalConverter(
new NamedCollectionConverter(
xstream2.getMapper(), "KEYWORD", String.class));

Cheers,
Jörg

Bruno Riev

unread,
Aug 20, 2015, 10:52:19 AM8/20/15
to XStream User, joerg.s...@swisspost.com
You nailed it, Jörg! It DOES work (both ways) if I manually register a LocalConverter: (Bean to XML) and (XML to Bean)

Please, allow me to repost fo scratch for future reference that others may seek as well:

=====================
Catalog.java
=====================

@XStreamAlias("CATALOG")
public class Catalog {

@XStreamAlias("PRODUCTS")
private List<Product> products;

public Catalog() {
this.products = new ArrayList<Product>();
}

public List<Product> getProducts() {
return this.products;
}

}

=====================
Product.java
=====================

@XStreamAlias("PRODUCT")
public class Product {

@XStreamAlias("PRODUCTID")
private String id;

@XStreamAlias("PRODUCTNAME")
private String name;

@XStreamAlias("DESCRIPTION")
private String description;

@XStreamAlias("KEYWORDS")
private List<String> keywords;

public Product() {
this.keywords = new ArrayList<String>();
}

// Getters and Setters
}


==========================================
Main Class (Bean to XML)
==========================================

public class App {

public static void main(String[] args) {

try {

// Bean to XML
XStream xstream2 = new XStream();
xstream2.processAnnotations(Catalog.class);
xstream2.processAnnotations(Product.class);

xstream2.registerLocalConverter(Product.class, "keywords",
new NamedCollectionConverter(xstream2.getMapper(), "KEYWORD", String.class));

Catalog catalog = new Catalog();
Product product = new Product();

product.setId("123");
product.setName("Test Name");
product.setDescription("Test Description");
product.getKeywords().add("keyword_1");
product.getKeywords().add("keyword_2");
product.getKeywords().add("keyword_3");

catalog.getProducts().add(product);

String XML = xstream2.toXML(catalog);
System.out.println(XML);
}

catch(Exception e) {
e.printStackTrace();
}

}
}

**** PRODUCES / PRINTS OUT ****:

<CATALOG>
<PRODUCTS>
<PRODUCT>
<PRODUCTID>123</PRODUCTID>
<PRODUCTNAME>Test Name</PRODUCTNAME>
<DESCRIPTION>Test Description</DESCRIPTION>
<KEYWORDS>
<KEYWORD>keyword_1</KEYWORD>
<KEYWORD>keyword_2</KEYWORD>
<KEYWORD>keyword_3</KEYWORD>
</KEYWORDS>
</PRODUCT>
</PRODUCTS>
</CATALOG>


==========================================
Main Class (XML to Bean)
==========================================

And given a Sample.XML file like this:

*********************
Sample.XML
*********************

<CATALOG>
<PRODUCTS>
<PRODUCT>
<PRODUCTID>123</PRODUCTID>
<PRODUCTNAME>Test Name</PRODUCTNAME>
<DESCRIPTION>Test Description</DESCRIPTION>
<KEYWORDS>
<KEYWORD>keyword_1</KEYWORD>
<KEYWORD>keyword_2</KEYWORD>
<KEYWORD>keyword_3</KEYWORD>
</KEYWORDS>
</PRODUCT>
</PRODUCTS>
</CATALOG>


*********************
Main App
*********************

public class App {

public static void main(String[] args) {

String filepath = "Sample.xml";

try {

// XML to Bean
FileReader reader = new FileReader(filepath);
XStream xstream = new XStream();

xstream.processAnnotations(Catalog.class);
xstream.processAnnotations(Product.class);

xstream.registerLocalConverter(Product.class, "keywords",
new NamedCollectionConverter(xstream.getMapper(), "KEYWORD", String.class));

Catalog catalog = (Catalog) xstream.fromXML(reader);
System.out.println(catalog.getProducts().get(0).getKeywords());

}

catch(Exception e) {
e.printStackTrace();
}

}
}


**** PRODUCES / PRINTS OUT THIS LIST ****:

[keyword_1, keyword_2, keyword_3]


--

Like you said, it's a little bit odd, the annotation @XStreamConverter didn't resolve it right :o

But it definitely works if one register a localConverter :)

Thank you very much, Jörg. I owe you one!

Best regards,
Bruno Riev

Jörg Schaible

unread,
Aug 20, 2015, 6:50:18 PM8/20/15
to xstrea...@googlegroups.com
Hi Bruno,

Bruno Riev wrote:

> You nailed it, Jörg! It DOES work (both ways) if I manually register a
> LocalConverter: (Bean to XML) and (XML to Bean)

You can also use the annotation, if you set its "useImplicitType" parameter
to false or you declare the field directly of type ArrayList. The
NamedCollectionConverter uses this "implicit type" as first constructor
argument and forces the converter to handle this type only (not even derived
ones). In your case it is List (the declared type), but your instance is
ArrayList, i.e. typically you will set useImplicitType to false registering
a NamedCollectionConverter.

Cheers,
Jörg
Reply all
Reply to author
Forward
0 new messages