Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Jackson : Converting JSON property to nested Object with Dot Notation

I have a JSON like this

{ "id":1, "name":"Jack", "parent.id":2 }

Note the dot on "parent.id" property

Is it possible to map those JSON to the following classes ?

class Child {
    private int id;
    private String name;

    private Parent parent;

    //getter and setter methods
}

class Parent {
    private int id;
    private String name;

    //getter and setter methods
}

So the mapping result would be similar to following statements:

Parent parent = new Parent();
parent.setId(2);

Child child = new Child();
child.setId(1);
child.setName("Jack");
child.setParent(parent); // Here is the result
like image 334
Fitrah M Avatar asked Jan 19 '12 04:01

Fitrah M


People also ask

How does Jackson read nested JSON?

A JsonNode is Jackson's tree model for JSON and it can read JSON into a JsonNode instance and write a JsonNode out to JSON. To read JSON into a JsonNode with Jackson by creating ObjectMapper instance and call the readValue() method. We can access a field, array or nested object using the get() method of JsonNode class.

How do I map nested values with Jackson?

Mapping With Annotations To map the nested brandName property, we first need to unpack the nested brand object to a Map and extract the name property. To map ownerName, we unpack the nested owner object to a Map and extract its name property.

How does Jackson convert object to JSON?

Converting Java object to JSON In it, create an object of the POJO class, set required values to it using the setter methods. Instantiate the ObjectMapper class. Invoke the writeValueAsString() method by passing the above created POJO object. Retrieve and print the obtained JSON.

How do I convert a JSON list to Jackson?

We can convert a List to JSON array using the writeValueAsString() method of ObjectMapper class and this method can be used to serialize any Java value as a String.


1 Answers

you can convert this

{ "id":1, "name":"Jack", "parent.id":2 }

into this

{ "id":1, "name":"Jack", "parent": { "id":2 } }

by using this

// I'm using jQuery here 
$.fn.serializeObject = function() {
  var arrayData, objectData;
  arrayData = this.serializeArray();
  objectData = {};

  $.each(arrayData, function() {
    var value;

    if (this.value != null) {
      value = this.value;
    } else {
      value = '';
    }

    // search for "parent.id" like attribute
    if (this.name.indexOf('.') != -1) {
      var attrs = this.name.split('.');
      var tx = objectData;

      for (var i = 0; i < attrs.length - 1; i++) {
        if (objectData[attrs[i]] == undefined)
          objectData[attrs[i]] = {};
        tx = objectData[attrs[i]];
      }
      tx[attrs[attrs.length - 1]] = value;
    } else {
      if (objectData[this.name] != null) {
        if (!objectData[this.name].push) {
          objectData[this.name] = [objectData[this.name]];
        }

        objectData[this.name].push(value);
      } else {
        objectData[this.name] = value;
      }
    }
  });

  return objectData;
};

and then you can serialize your code by using JSON.serialize().

if you are using Jackson, then you can deserialize the JSON request string by doing any of these:

1. create a custom Jackson deserialize module

2. parse the JSON yourself

public Child parseJackson(String jsonRequest) {
  // what we need
  ObjectMapper mapper;
  JsonNode root, parentNode;

  // your models
  Child child;
  Parent parent;

  // assign
  mapper = new ObjectMapper();
  root = mapper.readTree(jsonRequest); // deserialize JSON as tree
  parentNode = root.get("parent"); // get the "parent" branch

  // assign (again)
  child = mapper.readValue(root, Child.class);
  parent = mapper.readValue(parentNode, Parent.class);

  child.setParent(parent);

  return child;
}

the downside of this method is you have to parse for every single JsonRequest with nested objects and it will be messy when there's a complex nested structure. If this is a problem, I suggest you do the #3

3. create a custom Jackson ObjectMapper class to automate this process

The idea is to build generic process for #2 so that it could handle any nested request.

public class CustomObjectMapper extends ObjectMapper {

  // here's the method you need
  @Override
  public <T> T readValue(String src, Class<T> type)
      throws IOException, JsonParseException, JsonMappingException {

    JsonNode root = this.readTree(src);
    try {
      return readNestedValue(root, type);
    } catch (InstantiationException | IllegalAccessException | IOException
        | IllegalArgumentException | InvocationTargetException e) {
      return super.readValue(src, type);
    }

  }

  // if you're using Spring, I suggest you implement this method as well
  // since Spring's MappingJacksonHttpMessageConverter class will call 
  // this method.
  @Override
  public <T> T readValue(InputStream src, JavaType type)
      throws IOException, JsonParseException, JsonMappingException {

    JsonNode root = this.readTree(src);
    try {
      return readNestedValue(root, (Class<T>) type.getRawClass());
    } catch (InstantiationException | IllegalAccessException | IOException
        | IllegalArgumentException | InvocationTargetException e) {
      return super.readValue(src, type);
    }

  }

  // we need this to recursively scan the tree node
  protected <T> T readNestedValue(JsonNode root, Class<T> type)
      throws InstantiationException, IllegalAccessException, IOException,
        IllegalArgumentException, InvocationTargetException {

    // initialize the object use ObjectMapper's readValue
    T obj = super.readValue(root, type);
    Iterator it = root.getFieldNames();
    while (it.hasNext()) {
      String name = (String) it.next();
      String camelCaseName = name.substring(0, 1).toUpperCase() + name.substring(1);
      JsonNode node = root.get(name);

      Field f;
      try {
        f = type.getDeclaredField(name);
      } catch (NoSuchFieldException e) {
        f = findFieldInSuperClass(name, type.getSuperclass());
      }
      // if no field found then ignore
      if (f == null) continue; 

      Method getter, setter;
      try {
        getter = type.getMethod("get" + camelCaseName);
      } catch (NoSuchMethodException e) {
        getter = findGetterInSuperClass("get" + camelCaseName, type.getSuperclass());
      }
      // if no getter found or it has been assigned then ignore
      if (getter == null || getter.invoke(obj) != null) continue;

      try {
        setter = type.getMethod("set" + camelCaseName);
      } catch (NoSuchMethodException ex) {
        setter = findSetterInSuperClass("set" + camelCaseName, type.getSuperclass(), f.getType());
      }
      // if no setter found then ignore
      if (setter == null) continue;

      setter.invoke(obj, readNestedValue(node, f.getType()));
    }

    return obj;
  }

  // we need this to search for field in super class
  // since type.getDeclaredField() will only return fields that in the class
  // but not super class
  protected Field findFieldInSuperClass(String name, Class sClass) {
    if (sClass == null) return null;
    try {
      Field f = sClass.getDeclaredField(name);
      return f;
    } catch (NoSuchFieldException e) {
      return findFieldInSuperClass(name, sClass.getSuperclass());
    }
  }

  protected Method findGetterInSuperClass(String name, Class sClass) {
    if (sClass == null) return null;
    try {
      Method m = sClass.getMethod(name);
      return m;
    } catch (NoSuchMethodException e) {
      return findGetterInSuperClass(name, sClass.getSuperclass());
    }
  }

  protected Method findSetterInSuperClass(String name, Class sClass, Class type) {
    if (sClass == null) return null;
    try {
      Method m = sClass.getMethod(name, type);
      return m;
    } catch (NoSuchMethodException e) {
      return findSetterInSuperClass(name, sClass.getSuperclass(), type);
    }
  }
}

If you're using Spring, then the final step is registering this class as Spring bean.

<mvc:annotation-driven>
    <mvc:message-converters>
      <bean class="org.springframework.http.converter.json.MappingJacksonHttpMessageConverter">
         <property name="objectMapper">
            <bean class="x.y.z.CustomObjectMapper"/>
         </property>
      </bean>
    </mvc:message-converters>
  </mvc:annotation-driven>

with these set up you can easily use

@RequestMapping("/saveChild.json")
@ResponseBody
public Child saveChild(@RequestBody Child child) {
  // do something with child
  return child;
}

Hope this helps :)

like image 137
Iqbal Djulfri Avatar answered Oct 20 '22 00:10

Iqbal Djulfri