Reputation: 165
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?
Upvotes: 4
Views: 1233
Reputation: 53461
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.
Upvotes: 1
Reputation: 22291
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.
Upvotes: 1
Reputation: 3270
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).
Upvotes: 1