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

简介:

从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 是为了处理下划线转驼峰。不传则默认不转换。
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("")
}

}

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

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

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

一个类有一个名字空间 namespace ;
有一系列属性,每个属性有属性名与属性值,可称为 LeafNode;
有一系列 子节点类 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 的实现如下。有几点值得提一下:

策略模式。分离了三种情况(基本类型、Map, List)的处理。当有多重 if-else 语句,且每个分支都有大段代码达到同一个目标时,就可以考虑策略模式处理了。
构建器。将 ClassNode 的构建单独分离到 ClassNodeBuilder 。
组合模式。树形结构的处理,特别适合组合模式。
命名构造。使用命名构造器,从而免写了一些构造器。
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 串中自动生成对应的对象模型,使得这个过程自动化,让类似事情的效率成倍的增长了。原来可能要花费几十分钟甚至一个小时之多,现在不到三秒。

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

作者:@琴水玉

转载请注明出处:https://www.cnblogs.com/lovesqcc/p/12617323.html

相关文章
|
3月前
|
JSON JavaScript 前端开发
JavaScript实现字符串转json对象的方法
JavaScript实现字符串转json对象的方法
|
1月前
|
JSON 前端开发 JavaScript
json字符串如何转为list对象?
json字符串如何转为list对象?
226 7
|
1月前
|
JSON JavaScript 前端开发
js如何格式化一个JSON对象?
js如何格式化一个JSON对象?
72 3
|
6月前
|
JSON NoSQL MongoDB
实时计算 Flink版产品使用合集之要将收集到的 MongoDB 数据映射成 JSON 对象而非按字段分割,该怎么操作
实时计算Flink版作为一种强大的流处理和批处理统一的计算框架,广泛应用于各种需要实时数据处理和分析的场景。实时计算Flink版通常结合SQL接口、DataStream API、以及与上下游数据源和存储系统的丰富连接器,提供了一套全面的解决方案,以应对各种实时计算需求。其低延迟、高吞吐、容错性强的特点,使其成为众多企业和组织实时数据处理首选的技术平台。以下是实时计算Flink版的一些典型使用合集。
|
2月前
|
XML JSON JavaScript
JSON对象的stringify()和parse()方法使用
本文阐述了JSON对象的`stringify()`和`parse()`方法的用法,包括如何将JavaScript对象转换为JSON字符串,以及如何将JSON字符串解析回JavaScript对象,并讨论了转换过程中需要注意的事项。
JSON对象的stringify()和parse()方法使用
|
2月前
|
JSON 前端开发 中间件
React读取properties配置文件转化为json对象并使用在url地址中
本文介绍了如何在React项目中读取properties配置文件,将其内容转化为JSON对象,并在请求URL地址时使用这些配置。文章详细说明了异步读取文件、处理字符串转换为JSON对象的过程,并提供了一个封装函数,用于在发起请求前动态生成配置化的URL地址。
77 1
|
3月前
|
JSON C语言 数据格式
Python导出隐马尔科夫模型参数到JSON文件C语言读取
Python导出隐马尔科夫模型参数到JSON文件C语言读取
26 1
|
4月前
|
存储 JSON 测试技术
python中json和类对象的相互转化
针对python中类对象和json的相关转化问题, 本文介绍了4种方式,涉及了三个非常强大的python库jsonpickle、attrs和cattrs、pydantic,但是这些库的功能并未涉及太深。在工作中,遇到实际的问题时,可以根据这几种方法,灵活选取。 再回到结构化测试数据的构造,当需要对数据进行建模时,也就是赋予数据业务含义,pydantic应该是首选,目前(2024.7.1)来看,pydantic的生态非常活跃,各种基于pydantic的工具也非常多,建议尝试。
|
5月前
|
Web App开发 JSON JavaScript
JavaScript对象常用操作JSON总结
JavaScript对象常用操作JSON总结
39 8
|
5月前
|
JSON Java fastjson
老程序员分享:java对象转json
老程序员分享:java对象转json
173 3