天天看點

從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

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,子節點類可以遞歸處理。

實際上,對象模型符合樹形結構。可以定義一個對象模型的表示:

import org.apache.commons.collections.CollectionUtils

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

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

繼續閱讀