How to make XStream skip unmapped tags when parsing XML?
I have a large XML document that I want to convert to a Java bean. It has a lot of tags a开发者_运维知识库nd attributes, but I'm interested only in a handful of those. Unfurtounately, it seems that XStream forces you to declare a property in that bean for each and every tag that may ever be in that XML. Is there a way around this?
Initialize XStream
as shown below to ignore fields that are not defined in your bean.
XStream xstream = new XStream() {
@Override
protected MapperWrapper wrapMapper(MapperWrapper next) {
return new MapperWrapper(next) {
@Override
public boolean shouldSerializeMember(Class definedIn, String fieldName) {
if (definedIn == Object.class) {
return false;
}
return super.shouldSerializeMember(definedIn, fieldName);
}
};
}
};
XStream 1.4.5 makes you simple to deal with unknown tags. Use ignoreUnknownElements()
for tags which are not implemented yet or has been removed and you are dealing with old xml. You can also specify which particular tag you would like to ignore.
Since XStream 1.4.5 durring marshaller declaration it's enough to use ignoreEnknownElements() method:
XStreamMarshaller marshaller = new XStreamMarshaller();
marshaller.getXStream().ignoreUnknownElements();
...
to ignore unnecessary elements.
I've been working my way around this problem today and what I've found out is that using return this.realClass(fieldName) != null;
is not (always) a working solution however actually there is a way for XStream to skip unmapped tags AND work with implicit collections at the same time.
Why realClass(fieldName)
thing won't work
In fact trick with using
try {
return this.realClass(fieldName) != null;
} catch (Throwable t) {
return false;
}
works. What it does is attempt to guess the type by the tag name, see whether it succeeded and if not - returns false. So it'll perfectly skip tags like
<someUnknownTag>someContent</someUnknownTag>
BUT it will work only up to the moment (!) when somehow some "not needed" tag will happen to have a meaningful name for which realClass(fieldName)
will be actually able to return something not equal to null
and that tag won't be a member of any ImplicitCollection of yours. In that case knowing that the class for an xml element could be defined and there is no such field mapped in users type XStream will decide that "maybe this element is from some implicit collection". And it will fail very soon if there is neither such collection nor a field in your class. In my case the problematic piece of xml was like this:
<url>http://somewhere.com</url>
and, of course, there was neither Url url;
nor @XStreamImplicit List<Url> url
on my class. The result of having such an XML and using "realClass" thing is as follows:
com.thoughtworks.xstream.converters.ConversionException: Element url of type java.net.URL is not defined as field in type org.sample.xstream.SomeBean
The right way
The right way would be returning plain false
from shouldSerializeMember
in case when definedIn == Object.class
(not using realClass(fieldName)
stuff).
But just using return false
alone is not enough. I this form it will cause XStream to leave implicit collections empty.
The trick here is to make sure that one uses @XStreamImplicit(itemFieldName = "something")
instead of just using @XStreamImplicit
without parameters even in the cases when tag name and collection's generic param type have the same name.
So the right code will look like this:
xstream = new XStream() {
@Override
protected MapperWrapper wrapMapper(MapperWrapper next) {
return new MapperWrapper(next) {
@Override
public boolean shouldSerializeMember(Class definedIn, String fieldName) {
if (definedIn == Object.class) {
//This is not compatible with implicit collections where item name is not defined
return false;
} else {
return super.shouldSerializeMember(definedIn, fieldName);
}
}
};
}
};
xsteam.processAnnotations(SomeRootEntry.class);
And you need to be sure that in your classes your implicit collections are marked like this:
@XStreamImplicit(itemFieldName = "something")
private List <Something> somethingList;
Notice that itemFieldName
is explicitly specified even though List's generic type parameter has the same name. This is crucial.
In this case upon encountering <something>
tag XStream won't even visit your shouldSerializeMember
with that fieldName. It will just know in advance that the element is from implicit collections.
When it will visit your method is upon encountering <url>http://somewhere.com</url>
again. But here we're safe since we just return false
.
Works for me! Give it a try.
Use ignoreUnknownElements() method in your XStream instance:
XStream xstream = new XStream();
xstream.ignoreUnknownElements();
精彩评论