Jackson : Converting JSON property to nested Object with Dot Notation

后端 未结 1 1455
被撕碎了的回忆
被撕碎了的回忆 2021-02-03 12:37

I have a JSON like this

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

Note the dot on \"parent.id\" property

Is it

相关标签:
1条回答
  • 2021-02-03 13:20

    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 :)

    0 讨论(0)
提交回复
热议问题