从JSON中自动生成对应的对象模型

编程的乐趣和挑战之一,就是将体力活自动化,使效率成十倍百倍的增长。

需求

做一个项目,需要返回一个很大的 JSON 串,有很多很多很多字段,有好几层嵌套。前端同学给了一个 JSON 串,需要从这个 JSON 串建立对应的对象模型。
比如,给定 JSON 串:

{"error":0,"status":"success","date":"2014-05-10","extra":{"rain":3,"sunny":2},"recorder":{"name":"qin","time":"2014-05-10 22:00","mood":"good","address":{"provice":"ZJ","city":"nanjing"}},"results":[{"currentCity":"南京","weather_data":[{"date":"周六今天,实时19","dayPictureUrl":"http://api.map.baidu.com/images/weather/day/dayu.png","nightPictureUrl":"http://api.map.baidu.com/images/weather/night/dayu.png","weather":"大雨","wind":"东南风5-6级","temperature":"18"},{"date":"周日","dayPictureUrl":"http://api.map.baidu.com/images/weather/day/zhenyu.png","nightPictureUrl":"http://api.map.baidu.com/images/weather/night/duoyun.png","weather":"阵雨转多云","wind":"西北风4-5级","temperature":"21~14"}]}]}  

解析出对应的对象模型:

public class Domain implements Serializable {      private Integer error;      private String status;      private String date;      private List<Result> Results;      private Extra extra  }      public class Extra implements Serializable {      private Integer rain;      private Integer sunny;  }        public class Recorder implements Serializable {      private String name;      private String time;      private String mood;      private Address address  }      public class Address implements Serializable {      private String provice;      private String city;  }        public class Result implements Serializable {      private String currentCity;      private List<Weather_data> Weather_datas;    }      public class Weather_data implements Serializable {      private String date;      private String dayPictureUrl;      private String nightPictureUrl;      private String weather;      private String wind;      private String temperature;  }  

怎么办呢 ? 那么复杂的 JSON 串,手写的话,估计得写两个小时吧,又枯燥又容易出错。能否自动生成呢 ?

算法分析

显然,需要遍历这个 JSON ,分三种情形处理:

  1. 值为基本类型: 解析出对应的类型 type 和 字段名 name
  2. 值为 JSON 串: 需要递归处理这个 JSON 串
  3. 值为 List : 简单起见,取第一个元素,如果是基本类型,按基本类型处理,类型为 List[Type] ;如果是 JSON ,则类型为 List[ClassName],然后再递归处理这个 JSON。

一个代码实现

第一版程序如下,简单直接。这里用到了一些知识点:

  • 字符串中的变量引用和方法调用: "${indent()}private ${getType(v)} $k;n"
  • 最简单的模板引擎: SimpleTemplateEngine
  • 函数式编程: 在 parseMap 方法中传入 keyConverter 是为了处理下划线转驼峰。不传则默认不转换。
  • JSON 转换为对象: jsonSlurper.parseText(json)

JsonParser.groovy

package cc.lovesq.study.json    import groovy.json.JsonSlurper  import static cc.lovesq.study.json.Common.*    class JsonParser {        def jsonSlurper = new JsonSlurper()        def parse(json) {          def obj = jsonSlurper.parseText(json)          Map map = (Map) obj          parseMap(map, 'Domain', Common.&underscoreToCamelCase)      }        def parseMap(Map map, String namespace, keyConverter) {          def classTpl = classTpl()          def fields = ""          map.each {              k, v ->                  if (!(v instanceof Map) && !(v instanceof List)) {                      fields += "${indent()}private ${getType(v)} $k;n"                  }                  else {                        if (v instanceof Map) {                          def className = getClsName(k)                          fields += "${indent()}private $className $k;n"                          parseMap(v, convert(className, keyConverter), keyConverter)                      }                        if (v instanceof List) {                          def obj = v.get(0)                          if (!(obj instanceof Map) && !(obj instanceof List)) {                              def type = getType(obj)                              fields += "${indent()}private List<$type> ${type}s;n"                          }                          if (obj instanceof Map) {                              def cls = getClsName(k)                              if (cls.endsWith('s')) {                                  cls = cls[0..-2]                              }                              fields += "${indent()}private List<${convert(cls,keyConverter)}> ${cls}s;n"                              parseMap(obj, convert(cls, keyConverter), keyConverter)                          }                      }                  }          }          print getString(classTpl, ["Namespace": namespace, "fieldsContent" : fields])      }  }  

Common.groovy

package cc.lovesq.study.json    class Common {        def static getType(v) {          if (v instanceof String) {              return "String"          }          if (v instanceof Integer) {              return "Integer"          }          if (v instanceof Boolean) {              return "Boolean"          }          if (v instanceof Long) {              return "Long"          }          if (v instanceof BigDecimal) {              return "Double"          }            "String"      }        def static getClsName(String str) {          capitalize(str)      }        def static capitalize(String str) {          str[0].toUpperCase() + (str.length() >= 2 ? str[1..-1] : "")      }        def static uncapitalize(String str) {          str[0].toLowerCase() + (str.length() >= 2 ? str[1..-1] : "")      }        def static classTpl() {          '''  public class $Namespace implements Serializable {  $fieldsContent  }            '''      }        def static indent() {          ' '      }        def static getString(tplText, binding) {          def engine = new groovy.text.SimpleTemplateEngine()          return engine.createTemplate(tplText).make(binding).toString()      }        def static convert(key, convertFunc) {          convertFunc == null ? key : convertFunc(key)      }        def static underscoreToCamelCase(String underscore){          String[] ss = underscore.split("_")          if(ss.length ==1){              return underscore          }            return ss[0] + ss.collect { capitalize(it) }.join("")      }  }  

构建与表示分离

第一版的程序简单直接,但总感觉有点粗糙。整个处理混在一起,后续要修改恐怕比较困难。能不能更清晰一些呢 ?

可以考虑将构建与表示分离开。

表示

仔细再看下对象模型,可以归结出三个要素:

  1. 一个类有一个名字空间 namespace ;
  2. 有一系列属性,每个属性有属性名与属性值,可称为 LeafNode;
  3. 有一系列 子节点类 ClassNode,子节点类可以递归处理。

实际上,对象模型符合树形结构。可以定义一个对象模型的表示:

package cc.lovesq.study.json    import org.apache.commons.collections.CollectionUtils    import static cc.lovesq.study.json.Common.*    class ClassNode implements Node {        String className = ""      List<LeafNode> leafNodes = []      List<ClassNode> classNodes = []      Boolean isInList = false        @Override      String desc() {          def clsTpl = Common.classTpl()            def fields = ""          fields += leafNodes.collect { indent() + it.desc() }.join("n")          def classDef = getString(clsTpl, ["Namespace": className, "fieldsContent" : fields])          if (CollectionUtils.isEmpty(classNodes)) {              return classDef          }            fields += "n" + classNodes.find { it.isInList == false }.collect { "${indent()}private ${it.className} ${uncapitalize(it.className)}" }.join("n")          def resultstr = getString(clsTpl, ["Namespace": className, "fieldsContent" : fields])          resultstr += classNodes.collect { it.desc() }.join("n")          return resultstr      }        boolean addNode(LeafNode node) {          leafNodes.add(node)          true      }        boolean addNode(ClassNode classNode) {          classNodes.add(classNode)          true      }  }    class LeafNode implements Node {        String type      String name      Boolean isList = false        @Override      String desc() {          isList ? Common.getString("private List<$type> $name;", ["type": type, "name": name]) :                  Common.getString("private $type $name;", ["type": type, "name": name])      }    }    interface Node {     String desc()  }  

在 Node 定义了一个描述自己的方法 desc , LeafNode 和 ClassNode 分别实现自己的 desc 。这样,就完成了对象模型的表示。

接下来,需要完成 ClassNode 的构建。这个过程与第一版的基本类似,只是从直接打印信息变成了添加节点。

构建

构建 ClassNode 的实现如下。有几点值得提一下:

  1. 策略模式。分离了三种情况(基本类型、Map, List)的处理。当有多重 if-else 语句,且每个分支都有大段代码达到同一个目标时,就可以考虑策略模式处理了。
  2. 构建器。将 ClassNode 的构建单独分离到 ClassNodeBuilder 。
  3. 组合模式。树形结构的处理,特别适合组合模式。
  4. 命名构造。使用命名构造器,从而免写了一些构造器。

ClassNodeBuilder.groovy

package cc.lovesq.study.json    import groovy.json.JsonSlurper    import static cc.lovesq.study.json.Common.*    class ClassNodeBuilder {        def jsonSlurper = new JsonSlurper()        def build(json) {          def obj = jsonSlurper.parseText(json)          Map map = (Map) obj          return parseMap(map, 'Domain')      }        def static parseMap(Map map, String namespace) {          ClassNode classNode = new ClassNode(className: namespace)          map.each {              k, v ->                  getStratgey(v).add(classNode, k, v)          }          classNode      }        def static plainStrategy = new AddLeafNodeStrategy()      def static mapStrategy = new AddMapNodeStrategy()      def static listStrategy = new AddListNodeStrategy()        def static getStratgey(Object v) {          if (v instanceof Map) {              return mapStrategy          }            if (v instanceof List) {              return listStrategy          }          return plainStrategy      }        interface AddNodeStrategy {          def add(ClassNode classNode, k, v)      }        static class AddLeafNodeStrategy implements AddNodeStrategy {            @Override          def add(ClassNode classNode, Object k, Object v) {              classNode.addNode(new LeafNode(type: getType(v), name: k))          }      }        static class AddMapNodeStrategy implements AddNodeStrategy {            @Override          def add(ClassNode classNode, Object k, Object v) {              v = (Map)v              def className = getClsName(k)              classNode.addNode(parseMap(v, className))          }      }        static class AddListNodeStrategy implements AddNodeStrategy {            @Override          def add(ClassNode classNode, Object k, Object v) {              v = (List)v              def obj = v.get(0)              if (!(obj instanceof Map) && !(obj instanceof List)) {                  def type = getType(obj)                  classNode.addNode(new LeafNode(type: "$type", name: "${type}s", isList: true))              }              if (obj instanceof Map) {                  def cls = getClsName(k)                  if (cls.endsWith('s')) {                      cls = cls[0..-2]                  }                  classNode.addNode(new LeafNode(type: "${cls}", name: "${cls}s", isList:  true))                    def subClassNode = parseMap(obj, cls)                  subClassNode.isInList = true                  classNode.addNode(subClassNode)              }          }      }    }    

小结

通过编写程序,从 JSON 串中自动生成对应的对象模型,使得这个过程自动化,让类似事情的效率成倍的增长了。原来可能要花费几十分钟甚至一个小时之多,现在不到三秒。

让效率成倍增长的有效之法就是提升代码和方案的复用性,自动化手工处理。在日常工作中,是否可以想到办法,让手头事情的处理效率能够十倍百倍的增长呢 ? 这个想法看似有点疯狂,实际上,更多的原因是人们没有这么思考过吧。