问题描述
public class Xml {
public static void main(String[] args) throws JsonProcessingException {
String xmlString = "<password><plainPassword>12345</plainPassword></password>";
XmlMapper xmlMapper = new XmlMapper();
PlainPassword plainPassword = xmlMapper.readValue(xmlString,PlainPassword.class);
System.out.println(plainPassword.getPlainPassword());
}
@JacksonXmlRootElement(localName = "password")
public static class PlainPassword {
public String getPlainPassword() {
return this.plainPassword;
}
public void setPlainPassword(String plainPassword) {
this.plainPassword = plainPassword;
}
private String plainPassword;
}
}
它可以正常工作,但是在xmlString
中,我可以使用任何根标记名称,并且我的代码仍然可以使用。
例如,我使用String xmlString = "<x><plainPassword>12345</plainPassword></x>";
作为根元素的x
也可以工作。
但是,可以说xmlMapper可以正确地反序列化只有带有“密码”根元素的字符串吗?
解决方法
我会以不同的方式处理这个问题。获取一个 XPath 实现,选择所有匹配 //plainPassword
的节点,然后获取每个节点的内容列表。
如果需要,还可以获取父节点的名称;在找到节点的上下文中使用 ..
获取父节点。
检查 XPath examples 并亲自尝试一下。请注意,您的代码可能因语言和 XPath 实现而异。
,不幸的是,您描述的行为是 Jackson 支持的行为,如 this Github open issue 中所示。
使用 JSON 内容和 ObjectMapper
,您可以启用 UNWRAP_ROOT_VALUE
反序列化功能,也许它可以帮助实现此目的,尽管我不太确定此功能是否被正确支持XmlMapper
。
一种可能的解决方案是实现自定义解串器。
鉴于您的 PlainPassword
课程:
@JacksonXmlRootElement(localName = "password")
public class PlainPassword {
public String getPlainPassword() {
return this.plainPassword;
}
public void setPlainPassword(String plainPassword) {
this.plainPassword = plainPassword;
}
private String plainPassword;
}
考虑以下 main
方法:
public static void main(String[] args) throws JsonProcessingException {
String xmlString = "<x><plainPassword>12345</plainPassword></x>";
XmlMapper xmlMapper = new XmlMapper();
xmlMapper.registerModule(new SimpleModule().setDeserializerModifier(new BeanDeserializerModifier() {
@Override
public JsonDeserializer<?> modifyDeserializer(DeserializationConfig config,BeanDescription beanDesc,JsonDeserializer<?> deserializer) {
Class<?> beanClass = beanDesc.getBeanClass();
JacksonXmlRootElement annotation = beanClass.getAnnotation(JacksonXmlRootElement.class);
String requiredLocalName = null;
if (annotation != null) {
requiredLocalName = annotation.localName();
}
if (requiredLocalName != null) {
return new EnforceXmlElementNameDeserializer<>(deserializer,beanDesc.getBeanClass(),requiredLocalName);
}
return deserializer;
}
}));
PlainPassword plainPassword = xmlMapper.readValue(xmlString,PlainPassword.class);
System.out.println(plainPassword.getPlainPassword());
}
自定义解串器的样子:
public class EnforceXmlElementNameDeserializer<T> extends StdDeserializer<T> implements ResolvableDeserializer {
private final JsonDeserializer<?> defaultDeserializer;
private final String requiredLocalName;
public EnforceXmlElementNameDeserializer(JsonDeserializer<?> defaultDeserializer,Class<?> beanClass,String requiredLocalName) {
super(beanClass);
this.defaultDeserializer = defaultDeserializer;
this.requiredLocalName = requiredLocalName;
}
@Override
public T deserialize(JsonParser p,DeserializationContext ctxt)
throws IOException {
String rootName = ((FromXmlParser)p).getStaxReader().getLocalName();
if (!this.requiredLocalName.equals(rootName)) {
throw new IllegalArgumentException(
String.format("Root name '%s' does not match required element name '%s'",rootName,this.requiredLocalName)
);
}
@SuppressWarnings("unchecked")
T itemObj = (T) defaultDeserializer.deserialize(p,ctxt);
return itemObj;
}
@Override public void resolve(DeserializationContext ctxt) throws JsonMappingException {
((ResolvableDeserializer) defaultDeserializer).resolve(ctxt);
}
}
修改ResolvableDeserializer
时必须实现BeanDeserializer
,否则反序列化抛出异常。
代码基于 this excellent SO answer。
测试应该引发 IllegalArgumentException
和相应的消息:
Root name 'x' does not match required element name 'password'
请根据需要修改异常类型。
如果您使用:
String xmlString = "<password><plainPassword>12345</plainPassword></password>";
在您的 main
方法中,它应该可以正常运行。
您可以将根类的名称更改为所有名称,例如:@JacksonXmlRootElement(localName = "xyz")
,并且可以使用。
基于Java documentation JacksonXmlRootElement 用于定义用于根级别对象序列化时的根元素的名称(不适用于反序列化映射),通常使用类型(类)的名称。