从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 ,分三种情形处理:
- 值为基本类型: 解析出对应的类型 type 和 字段名 name
- 值为 JSON 串: 需要递归处理这个 JSON 串
- 值为 List : 简单起见,取第一个元素,如果是基本类型,按基本类型处理,类型为 List[Type] ;如果是 JSON ,则类型为 List[ClassName],然后再递归处理这个 JSON。
一个代码实现
第一版程序如下,简单直接。这里用到了一些知识点:
- 字符串中的变量引用和方法调用:
"${indent()}private ${getType(v)} $k;\n"
- 最简单的模板引擎: SimpleTemplateEngine
- 函数式编程: 在 parseMap 方法中传入 keyConverter 是为了处理下划线转驼峰。不传则默认不转换。
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("")
}
}
构建与表示分离
第一版的程序虽然简单直接,但总感觉有点粗糙。整个处理混在一起,后续要修改恐怕比较困难。能不能更清晰一些呢 ?
仔细再看下对象模型,可以归结出三个要素:
- 一个类有一个名字空间 namespace
- 有一系列 属性名与属性值;
- 有一系列 子节点类,子节点类可以递归处理。
实际上,对象模型符合树形结构。可以定义一个对象模型的表示:
class ClassNode implements Node {
String className
List<LeafNode> leafNodes
List<ClassNode> classNodes
Boolean isInList = false
}
class LeafNode implements Node {
String type
String name
Boolean isList = false
}
interface Node {
String desc()
}
在 Node 定义了一个描述自己的方法 desc , LeafNode 和 ClassNode 分别实现自己的 desc 。这样,就完成了对象模型的表示。
接下来,需要完成 ClassNode 的构建。这个过程与第一版的基本类似,只是从直接打印信息变成了添加节点。
第二个代码实现
第二版的程序如下。有几点值得提一下:
- 策略模式,分离是三种情况(基本类型、Map, List)的处理。当有多重 if-else 语句,且每个分支都有大段代码达到同一个目标时,就可以考虑策略模式处理了。
- 构建器。将 ClassNode 的构建单独分离到 ClassNodeBuilder 。
- 组合模式。树形结构的处理,特别适合组合模式。
ClassNode.groovy
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
ClassNode() {
this('')
}
ClassNode(name) {
className = name
leafNodes = []
classNodes = []
}
@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
}
}
LeafNode.groovy
package cc.lovesq.study.json
class LeafNode implements Node {
String type
String name
Boolean isList = false
LeafNode(type, name) {
this.type = type
this.name = name
}
LeafNode(type, name, isList) {
this.type = type
this.name = name
this.isList = isList
}
@Override
String desc() {
isList ? Common.getString("private List<$type> $name;", ["type": type, "name": name]) :
Common.getString("private $type $name;", ["type": type, "name": name])
}
}
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()
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(getType(v), 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}s", true))
}
if (obj instanceof Map) {
def cls = getClsName(k)
if (cls.endsWith('s')) {
cls = cls[0..-2]
}
classNode.addNode(new LeafNode("${cls}", "${cls}s", true))
def subClassNode = parseMap(obj, cls)
subClassNode.isInList = true
classNode.addNode(subClassNode)
}
}
}
}
JsonParserV2.groovy
package cc.lovesq.study.json
import groovy.json.JsonOutput
class JsonParserV2 {
def parse(json) {
def classNode = new ClassNodeBuilder().build(json)
println(JsonOutput.toJson(classNode))
print classNode.desc()
}
}
小结
通过编写程序,从 JSON 串中自动生成对应的对象模型,使得这个过程自动化,让类似事情的效率成倍的增长了。原来可能要花费几十分钟甚至一个小时之多,现在不到三秒。
让效率成倍增长的有效之法就是提升代码和方案的复用性,自动化手工处理。在日常工作中,是否可以想到办法,让手头事情的处理效率能够十倍百倍的增长呢 ? 这个想法看似有点疯狂,实际上,更多的原因是人们没有这么思考过吧。