I summarized the issues encountered while using jackson-dataformat-xml(2.12.4).
1. interface naming
'interface' is a reserved word in Java, making it a poor choice for an XML element name.
However, the existing code was using the 'interface' naming, and it was necessary to develop in accordance with the same 'interface' naming to maintain backward compatibility.
Therefore, I first attached the @XmlElement
annotation to the field to be bound, specifying the name as 'interface' like below.
@XmlElement(name = "interface")
private String interFace;
Since the Lombok library was used, the setter was automatically created with the naming 'setInterFace'.
public void setInterFace(String interFace) {
this.interFace = interFace;
}
However, the value of the XML 'interface' tag was not being bound correctly. Instead, changing the setter naming as follows resulted in successful binding. In other words, the 'f' was changed to lowercase.
public void setInterface(String interFace) {
this.interFace = interFace;
}
To understand the exact reason why the binding failed, I conducted debugging.
In attempting to find 'interface' within _beanProperties, no result appears, leading to the prop becoming null.
Consequently, an error occurs in handleUnknownVanilla with com.fasterxml.jackson.databind.exc.UnrecognizedPropertyException: Unrecognized field "interface"
.
Another solution to this problem is utilizing the _caseInsensitive attribute found in _beanProperties.
By adding the configuration objectMapper.configure(MapperFeature.ACCEPT_CASE_INSENSITIVE_PROPERTIES, true)
, it becomes possible to find in _beanProperties without distinguishing between uppercase and lowercase, thereby finding the properties correctly.
Additionally, using @JacksonXmlProperty
instead of @XmlElement
results in 'interface' being registered in _beanProperties, making it discoverable.
@JacksonXmlProperty(localName = "interface")
private String interFace;
However, We decided not to apply it in practice because adding the dependency on com.fasterxml.jackson.dataformat.xml.annotation
directly, rather than relying on javax.xml.bind.annotation
, was deemed not advisable.
2. list
If multiple XML tags of the same kind are input, they can be bound to a Java List object. However, if the same XML tags do not come in succession, the behavior differs. Let's look at the example code below.
package com.fasterxml.jackson.dataformat.xml.lists;
import java.util.List;
import javax.xml.bind.annotation.XmlElement;
import javax.xml.bind.annotation.XmlRootElement;
import com.fasterxml.jackson.annotation.JsonMerge;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.dataformat.xml.XmlMapper;
import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlCData;
import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlElementWrapper;
import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlProperty;
import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlRootElement;
public class ListTest {
public static void main(String[] args) throws JsonProcessingException {
String xml = ""
+ "<Data>"
+ " <product>a</product>"
+ " <product>b</product>"
+ " <backUrl>www.naver.com</backUrl>"
+ " <product>c</product>"
+ " <product>d</product>"
+ " <backUrl>www.naver.com</backUrl>"
+ " <product>e</product>"
+ "</Data>";
XmlMapper m = new XmlMapper();
// m.setDefaultMergeable(true);
Data data = m.readValue(xml, Data.class);
System.out.println(data.getProduct());
}
}
@JacksonXmlRootElement(localName = "data")
class Data {
@JacksonXmlCData
@JacksonXmlElementWrapper(useWrapping = false)
@JacksonXmlProperty
private List<String> product;
@JacksonXmlCData
@JacksonXmlElementWrapper(useWrapping = false)
@JacksonXmlProperty
private String backUrl;
public List<String> getProduct() {
return product;
}
public void setProduct(List<String> product) {
this.product = product;
}
public String getBackUrl() {
return backUrl;
}
public void setBackUrl(String backUrl) {
this.backUrl = backUrl;
}
}
Multiple 'product' tags are supposed to be bound to a List object, but when actually calling getProduct, only 'e' appears. Although the XML request format was not standard, proper List object binding occurred with the older jaxb-api(2.1)
, necessitating a solution for this issue within jackson-dataformat-xml
as well.
The first thing we can do is modify the setProduct method as follows. Changing it to add the product instead of overwriting it will yield the desired result.
public void setProduct(List<String> product) {
if (this.product == null) {
this.product = product;
} else {
this.product.addAll(product);
}
}
Another method exists as well. From jackson-dataformat-xml(2.9)
onwards, you can use the provided setDefaultMergeable.
Result without setDefaultMergeable true setting: [e]
Result with setDefaultMergeable true setting: [a, b, c, d, e]
However, there's one point of caution when using setDefaultMergeable. If the getter for the product is implemented as below to defend against null, then since the initial this.product is null, the result of the getProduct method becomes EMPTY_LIST.
public List<String> getProduct() {
return Objects.isNull(this.product) ?
Collections.emptyList() :
Collections.unmodifiableList(this.product);
}
Internally, when merging, the getProduct method is called, but since it initially receives EMPTY_LIST as a result, addition doesn't occur. This leads to an error: org.springframework.http.converter.HttpMessageNotReadableException: JSON parse error: (was java.lang.UnsupportedOperationException); nested exception is com.fasterxml.jackson.databind.JsonMappingException: (was java.lang.UnsupportedOperationException)
, even when using @JsonMerge
.
Therefore, either implement getProduct in its basic form or
public List<String> getProduct() {
return this.product;
}
work as follows to prevent a null return.
public List<String> getProduct() {
if (this.product == null) {
this.product = new ArrayList<>();
}
return this.product;
}
Finally, XmlMapper is a subclass of ObjectMapper. Setting XmlMapper.setDefaultMergeable(true)
is fine because it calls the setDefaultMergeable method implemented by the superclass ObjectMapper.
Top comments (0)