Jackson: ignore whitespace in empty collection @XmlWrapperElement

Using Jackson and jackson-dataformat-xml 2.4.4 I am trying to deserialize an XML document where a collection annotated with @XmlWrapperElement can have null elements, but where the XML contains spaces (line break in my case) Jackson is throwing a JsonMappingException on this content with the message "Unable to deserialize instance java.util.ArrayList from VALUE_STRING marker". I cannot change the way the XML is generated.

Example:

static class Outer {
    @XmlElementWrapper
    List<Inner> inners;
}

static class Inner {
    @XmlValue
    String foo;
}
ObjectMapper mapper = new XmlMapper().registerModules(new JaxbAnnotationModule());
String xml = "<outer><inners>\n</inners></outer>";
Outer outer = mapper.readValue(xml, Outer.class);

      

The following workarounds don't work:

  • Inclusion DeserializationFeature.ACCEPT_SINGLE_VALUE_AS_ARRAY

    : In this case, Jackson wants to instantiate a fake instance Inner

    using spaces as content.
  • Create setters for this field for both String and collection type. In this case, I get a JsonMappingException ("Conflicting setter definition for property" inners ").
  • This StackOverflow question suggests downgrading Jackson to 2.2.3. This does not fix the problem for me.

Any suggestions?

Edit: I can work around this issue by wrapping a CollectionDeserializer and checking the skips token. It looks very fragile to me, for example. I had to override another method to bind the object. I can post a workaround, but a cleaner approach would be better.

+3


source to share


1 answer


A workaround for this problem is to wrap the standard CollectionDeserializer

one to return an empty collection for tokens containing spaces and register a new Deserializer. I put the code in Module

so that it can be easily logged:

import java.io.IOException;
import java.util.Collection;

import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.core.JsonToken;
import com.fasterxml.jackson.databind.BeanDescription;
import com.fasterxml.jackson.databind.BeanProperty;
import com.fasterxml.jackson.databind.DeserializationConfig;
import com.fasterxml.jackson.databind.DeserializationContext;
import com.fasterxml.jackson.databind.JsonDeserializer;
import com.fasterxml.jackson.databind.JsonMappingException;
import com.fasterxml.jackson.databind.deser.BeanDeserializerModifier;
import com.fasterxml.jackson.databind.deser.std.CollectionDeserializer;
import com.fasterxml.jackson.databind.module.SimpleModule;
import com.fasterxml.jackson.databind.type.CollectionType;


public class XmlWhitespaceModule extends SimpleModule {
    private static class CustomizedCollectionDeserialiser extends CollectionDeserializer {

        public CustomizedCollectionDeserialiser(CollectionDeserializer src) {
            super(src);
        }

        private static final long serialVersionUID = 1L;

        @SuppressWarnings("unchecked")
        @Override
        public Collection<Object> deserialize(JsonParser jp, DeserializationContext ctxt)
                throws IOException, JsonProcessingException {
            if (jp.getCurrentToken() == JsonToken.VALUE_STRING
                    && jp.getText().matches("^[\\r\\n\\t ]+$")) {
                return (Collection<Object>) _valueInstantiator.createUsingDefault(ctxt);
            }
            return super.deserialize(jp, ctxt);
        }

        @Override
        public CollectionDeserializer createContextual(DeserializationContext ctxt,
                BeanProperty property) throws JsonMappingException {
            return new CustomizedCollectionDeserialiser(super.createContextual(ctxt, property));
        }
    }

    private static final long serialVersionUID = 1L;

    @Override
    public void setupModule(SetupContext context) {
        super.setupModule(context);
        context.addBeanDeserializerModifier(new BeanDeserializerModifier() {
            @Override
            public JsonDeserializer<?> modifyCollectionDeserializer(
                    DeserializationConfig config, CollectionType type,
                    BeanDescription beanDesc, JsonDeserializer<?> deserializer) {
                if (deserializer instanceof CollectionDeserializer) {
                    return new CustomizedCollectionDeserialiser(
                        (CollectionDeserializer) deserializer);
                } else {
                    return super.modifyCollectionDeserializer(config, type, beanDesc,
                        deserializer);
                }
            }
        });
    }

}

      



After that, you can add it to yours ObjectMapper

like this:

ObjectMapper mapper = new XmlMapper().registerModule(new XmlWhitespaceModule());

      

+3


source







All Articles