Jackson JSON Deserialization with Root Element

Jackson JSON Deserialization with Root Element

edit: this solution only works for jackson < 2.0

For your case there is a simple solution:

  • You need to annotate your model class with @JsonRootName(value = "user");
  • You need to configure your mapper with om.configure(Feature.UNWRAP_ROOT_VALUE, true); (as for 1.9) and om.configure(DeserializationFeature.UNWRAP_ROOT_VALUE, true); (for version 2).

That's it!


@JsonRootName(value = "user")
public static class User {
private String name;
private Integer age;

public String getName() {
return name;
}

public void setName(final String name) {
this.name = name;
}

public Integer getAge() {
return age;
}

public void setAge(final Integer age) {
this.age = age;
}

@Override
public String toString() {
return "User [name=" + name + ", age=" + age + "]";
}

}

ObjectMapper om = new ObjectMapper();
om.configure(Feature.UNWRAP_ROOT_VALUE, true);
System.out.println(om.readValue("{ \"user\": { \"name\":\"Sam Smith\", \"age\":1 }}", User.class));

this will print:

User [name=Sam Smith, age=1]

Deserialize json using Java Jackson and based on the root element call different classes that match the Json Object within that root element

I tried to look at many examples and the documentation of Jackson. I was finally able to get it working. This is just an example that can be used as a basis for mapping the class. In my case I was trying to see what type of element was present based on which I was mapping to different classes. So this may not work exactly as you expect but still be good for reference.

I am posting the same here so it can be helpful to someone in the future:

If following is the JSON file content:

[
{
"isA": "Type1",
"name": "Test",
"foo": "val1",
"foo": "val2",
"bar": "val3",
"foo": {
"myField": "Value1",
"myField": "value2"
}
},
{
"isA": "Type2",
"name": "Test1",
"foo": "val1",
"foo": "val2",
"bar": "val3",
"foo": {
"myField": "Value1",
"myField": "value2"
}
}
]

public void xmlConverter(InputStream jsonStream) throws IOException {
// Get the JSON Factory and parser Object
final JsonParser jsonParser = new JsonFactory().createParser(jsonStream);
final ObjectMapper objectMapper = new ObjectMapper();
jsonParser.setCodec(objectMapper);

// Check the first element is ARRAY if not then invalid JSON throw error
if (jsonParser.nextToken() != JsonToken.START_ARRAY) {
throw new IllegalStateException("Expected content to be an array");
}

jsonParser.nextToken();

// Loop until the end of the events file
while (jsonParser.nextToken() != JsonToken.END_ARRAY) {

// Get the node
final JsonNode jsonNode = jsonParser.readValueAsTree();

//Check if the JsonNode is valid if not then exit the process
if (jsonNode == null || jsonNode.isNull()) {
System.out.println("End Of File");
break;
}

// Get the eventType
final String eventType = jsonNode.get("isA").asText();

// Based on eventType call different type of class
switch (eventType) {
case "Type1":
final Type1 type1Info = objectMapper.treeToValue(jsonNode, Type1.class);
break;
case "Type2":
final Type2 type2Info = objectMapper.treeToValue(jsonNode, Type2.class);
break;
default:
System.out.println("JSON event does not match any of event : " + eventType);
break;
}
}
}

Jackson json deserialization, with root element from json

the simple answer is to post

{
"instalacion":"1",
"tipoNotificacion":"2",
"mensaje":"Un Mensaje"
}

instead of

{
"notificacion":
{
"instalacion":"1",
"tipoNotificacion":"2",
"mensaje":"Un Mensaje"
}
}

Deserialize node to subtype based on root name with Jackson

At the Github page of Jackson someone asked for a solution for the the same problem as what you are facing, see here: https://github.com/FasterXML/jackson-databind/issues/1627

A work around for the time-being would be a custom deserializer where a field within the class should contain what type of class it is so it can be properly deserialized. See here for the work around: https://stackoverflow.com/a/50013090/6777695

xmlMapper allow to use any root element during deserialization

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.



Related Topics



Leave a reply



Submit