英文:
xmlMapper allow to use any root element during deserialization
问题
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 它只能正确反序列化带有 "password" 根元素的字符串?
英文:
I have such code
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;
}
}
It works fine, but in xmlString
I can use any root tag name and my code still will work.
For example String xmlString = "<x><plainPassword>12345</plainPassword></x>";
where I use x
as root element also works.
But is it possible to say xmlMapper that it could correctly deserialize only strings with "password" root element?
答案1
得分: 1
你可以将根类的名称更改为任何内容,例如:@JacksonXmlRootElement(localName = "xyz")
,它是有效的。
根据Java文档,JacksonXmlRootElement 用于定义在将根级对象序列化时用于根元素的名称(不用于反序列化映射),通常使用类型(类)的名称。
英文:
You can change your name of root class to everything, for example : @JacksonXmlRootElement(localName = "xyz")
and it works.
Based on Java documentation JacksonXmlRootElement is used to define name of root element used for the root-level object when serialized (not for deserialized mapping), which normally uses name of the type (class).
答案2
得分: 1
我会以不同的方式来处理这个问题。获取一个XPath的实现,选择所有匹配//plainPassword
的节点,然后获取每个节点的内容列表。
如果需要的话,你还可以获取父节点的名称;当在找到的节点上下文中时,使用..
来获取父节点。
查看XPath示例并自行尝试。请注意,根据编程语言和XPath实现的不同,你的代码可能会有所不同。
英文:
I'd approach this differently. Grab an XPath implementation, select all nodes that match //plainPassword
, then get a list of contents of each node.
If you need to, you can also get the name of the parent node; when in context of a found node use ..
to get the parent node.
Check XPath examples and try it out for yourself. Note that your code may differ depending on language and XPath implementation.
答案3
得分: 1
很抱歉,由于您要求仅返回翻译的部分,我将只返回翻译后的代码和内容,不包含其他内容:
不幸的是,您描述的行为是由Jackson支持的,正如[此Github开放问题](https://github.com/FasterXML/jackson-dataformat-xml/issues/247)中所示。
对于JSON内容和`ObjectMapper`,您可以启用[`UNWRAP_ROOT_VALUE`](https://github.com/FasterXML/jackson-databind/wiki/Deserialization-Features#structural-conversions)反序列化特性,或许对于此目的可能会有帮助,尽管我不太确定此特性是否由`XmlMapper`正确支持。
一个可能的解决方案是实现自定义反序列化器。
考虑您的`PlainPassword`类:
```java
@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) {
// ...
}
}));
PlainPassword plainPassword = xmlMapper.readValue(xmlString, PlainPassword.class);
System.out.println(plainPassword.getPlainPassword());
}
其中自定义的反序列化器如下:
public class EnforceXmlElementNameDeserializer<T> extends StdDeserializer<T> implements ResolvableDeserializer {
// ...
}
您必须在修改BeanDeserializer
时实现ResolvableDeserializer
,否则反序列化会引发异常。
这些代码基于这个优秀的SO答案。
测试应该引发带有相应消息的IllegalArgumentException
:
Root name 'x' does not match required element name 'password'
请根据需要修改异常类型。
如果您在main
方法中使用:
String xmlString = "<password><plainPassword>12345</plainPassword></password>";
则应该能够正常运行。
英文:
Unfortunately, the behavior you described is the one supported by Jackson as indicated in this Github open issue.
With JSON content and ObjectMapper
you can enable the UNWRAP_ROOT_VALUE
deserialization feature, and maybe it could be of help for this purpose, although I am not quite sure if this feature is or not correctly supported by XmlMapper
.
One possible solution could be the implementation of a custom deserializer.
Given your PlainPassword
class:
@JacksonXmlRootElement(localName = "password")
public class PlainPassword {
public String getPlainPassword() {
return this.plainPassword;
}
public void setPlainPassword(String plainPassword) {
this.plainPassword = plainPassword;
}
private String plainPassword;
}
Consider the following main
method:
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());
}
Where the custom deserializer looks like:
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);
}
}
You have to implement ResolvableDeserializer
when modifying BeanDeserializer
, otherwise deserializing throws exception.
The code is based in this excellent SO answer.
The test should raise IllegalArgumentException
with the corresponding message:
Root name 'x' does not match required element name 'password'
Please, modify the exception type as appropriate.
If, instead, you use:
String xmlString = "<password><plainPassword>12345</plainPassword></password>";
in your main
method, it should run without problem.
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论