问题
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