Jackson xml 2.9.0: @JacksonXmlElementWrapper not working with @JsonCreator & @JsonProperty constructor

时光总嘲笑我的痴心妄想 提交于 2021-01-29 03:06:26

问题


I would like that my ParentClass has final fields, 'brokenChildList' list is wrapped xml element and list items have different tag than the list (<brokenChildList><brokenChild/></brokenChildList>).

Here is a snippet of code to reproduce my issues (imports are partially truncated, setters and getters omitted)

import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.dataformat.xml.XmlMapper;
import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlElementWrapper;
import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlProperty;
public class Main {
    public static void main(String... args) throws IOException {
        ObjectMapper xmlMapper = new XmlMapper();
        String xmlString = "<ParentClass><childClass name=\"name1\" value=\"val1\"/><brokenChildList><brokenChild name=\"bc1\" reason=\"bc-val1\"/><brokenChild name=\"bc2\" reason=\"bc-val2\"/></brokenChildList></ParentClass>";
        ParentClass parentClass = xmlMapper.readValue(xmlString, ParentClass.class);
        StringWriter stringWriter = new StringWriter();
        xmlMapper.writeValue(stringWriter, parentClass);
        String serialised = stringWriter.toString();
        System.out.println(serialised);
        System.out.println(xmlString.equals(serialised));
    }

    public static class ChildClass {
        @JacksonXmlProperty(isAttribute = true)
        private String name;
        @JacksonXmlProperty(isAttribute = true)
        private String value;
        //getters & setters
    }
    public static class BrokenChild {
        @JacksonXmlProperty(isAttribute = true)
        private String name;
        @JacksonXmlProperty(isAttribute = true)
        private String reason;
        //getters & setters
    }
    public static class ParentClass {
        private final ChildClass childClass;
        private final List<BrokenChild> brokenChildList;
        @JsonCreator
        public ParentClass(
            @JsonProperty("childClass") ChildClass childClass,
            @JsonProperty("brokenChildList") List<BrokenChild> brokenChildList
        ) {
            this.childClass = childClass;
            this.brokenChildList = brokenChildList;
        }

        @JacksonXmlProperty(localName = "childClass")
        public ChildClass getChildClass() {
            return childClass;
        }

        @JacksonXmlElementWrapper(localName = "brokenChildList")
        @JacksonXmlProperty(localName = "brokenChild")
        public List<BrokenChild> getBrokenChildList() {
            return brokenChildList;
        }
    }
}

The above code gives output with Jackson version 2.8.10:

<ParentClass><childClass name="name1" value="val1"/><brokenChildList><brokenChild name="bc1" reason="bc-val1"/><brokenChild name="bc2" reason="bc-val2"/></brokenChildList></ParentClass>
true

With Jackson version 2.9.0 it gives:

Exception in thread "main" com.fasterxml.jackson.databind.JsonMappingException: Duplicate property 'brokenChildList' for [simple type, class org.test.Main$ParentClass]
 at [Source: (StringReader); line: 1, column: 1]

I would like to find a solution (and any version after 2.9.0) that will give same output with the attached code.

My failed attempts include:

  • Replacing @JacksonXmlElementWrapper(localName = "brokenChildList") with @JacksonXmlElementWrapper will rename wrapper element as 'brokenChild' which is undesirable.

  • Removing @JacksonXmlElementWrapper(localName = "brokenChildList") will rename wrapper element as 'brokenChild' which is undesirable.


回答1:


This problem is really tricky because Jackson collects metadata from different places: fields, getters, setters, constructor parameters. Also, you can use MixIn but in your case it does not appear.

@JacksonXmlElementWrapper annotation can be attached to FIELD and METHOD type elements and this forces you to declare it on getter. Because ParentClass is immutable and you want to build it with constructor we need to annotate constructor parameters as well. And this is where collision appears: you have a constructor parameter with @JsonProperty("brokenChildList") annotation and getter with @JacksonXmlElementWrapper(localName = "brokenChildList") which reuses the same name. If you would changed localName to @JacksonXmlElementWrapper(localName = "brokenChildListXYZ") (added XYZ) everything would be deserialised and serialised but output would be different then input.

To solve this problem, we can use com.fasterxml.jackson.databind.deser.BeanDeserializerModifier class which allows to filter out fields we do not want to use for deserialisation and which creates collision. Example usage:

import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.databind.BeanDescription;
import com.fasterxml.jackson.databind.DeserializationConfig;
import com.fasterxml.jackson.databind.deser.BeanDeserializerModifier;
import com.fasterxml.jackson.databind.introspect.BeanPropertyDefinition;
import com.fasterxml.jackson.databind.module.SimpleModule;
import com.fasterxml.jackson.dataformat.xml.XmlMapper;
import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlElementWrapper;
import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlProperty;

import java.io.IOException;
import java.io.StringWriter;
import java.util.List;
import java.util.stream.Collectors;

public class XmlMapperApp {

    public static void main(String... args) throws IOException {
        SimpleModule module = new SimpleModule();
        module.setDeserializerModifier(new BeanDeserializerModifier() {
            @Override
            public List<BeanPropertyDefinition> updateProperties(DeserializationConfig config, BeanDescription beanDesc, List<BeanPropertyDefinition> propDefs) {
                if (beanDesc.getBeanClass() == ParentClass.class) {
                    return propDefs.stream().filter(p -> p.getConstructorParameter() != null).collect(Collectors.toList());
                }
                return super.updateProperties(config, beanDesc, propDefs);
            }
        });
        XmlMapper xmlMapper = XmlMapper.xmlBuilder()
                .addModule(module)
                .build();

        //yours code
    }
}

To create this example I used version 2.10.0.

See also:

  • Jackson 2.10 features
  • Jackson Release 2.10


来源:https://stackoverflow.com/questions/59124646/jackson-xml-2-9-0-jacksonxmlelementwrapper-not-working-with-jsoncreator-js

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!