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?
Advertisement
Answer
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.