开发者

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();
0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜