接口自动化测试---通用JSON解析算法

JSON格式的数据在http接口的自动化测试中是一种常见的数据结构,在接口自动化脚本里解析JSON返回结果,验证数据的正确性是非常关键的一步,但JSON结构有简单有复杂,不同的接口返回不同的结果,自动化脚本中解析响应JSON数据占了一大部分的工作量,而且随着接口的变动,维护脚本也特别麻烦,今天在这里介绍一种通用的解析所有JSON 的通用算法,来解决这一问题。

算法解析:

首先来看一段较为复杂的JSON:

图-1

从图-1可以看出,这段JSON第一层包含了resultCode,resultDesc,data,dataList四个key值,其中resultCode,resultDesc,data 都是单个的字符串,而dataList 的值是一个列表,有俩元素,其中每个元素又是一个字典,包括id,name,caption,subjectId,notes,items,其中items中又是一个list,每个元素为一个字典,key值为 id,name,caption等....不难发现这段JSON中第二层和第三层中都包含相同的key: id,name, capiton,这也是选择这段JSON的原因。大多数的JSON中可能都会有相同的key,解析的时候如何精确的获取想要的key对应的值这也是需要考虑的场景之一。

其实JSON的整个结构就是 “树”,可以将上述JSON中的 第一个节点[JSON]看成树的根节点, resultCode,resultDesc 等节点看成叶子节点,而dataList 则可以看成父节点,dataList里的元素可以看成是子节点。

来一张更容易理解的图:A 为根节点,黄色的节点称是父节点,绿色节点则是叶子节点,图中的D 类比为图-1中的dataList节点,B,C为图一种的resultCode,resultDesc

图-2

解析JSON主要分为以下几步

第一步:

  • 将JSON 转化为树,从根节点(A)开始遍历每一路径,即从根节点到叶子节点每一条路径作为一条数据,并且获取每一深度(JSON中的每一层)叶子节点的{key:value},其中父节点的值不取(通常父节点可能是一个list,或者一个字典),当前深度的叶子节点的值全取,与下一深度的叶子节点的值加到同一字典中。
  • 如图-2,B,C,D,E是深度为2的节点,第一次遍历,第一条数据为{B,C},以此递归,直至遍历所有路径,可到图-2的遍历结果为(其中B节点理解为 {'B':'B'},图-3 为图-2的JSON格式)
图-3

按第一步解析结果如下:

1. [{'B': 'B'}, {'C': 'C'}]

2. [{'B': 'B'}, {'C': 'C'}, {'G': 'G'}, {'F': 'F'}]

3. [{'B': 'B'}, {'C': 'C'}, {'G': 'G'}, {'F': 'F'}, {'K': 'K'}, {'L': 'L'}, {'M': 'M'}]

4. [{'B': 'B'}, {'C': 'C'}, {'G': 'G'}, {'F': 'F'}, {'O': 'O'}, {'P': 'P'}]

5. [{'B': 'B'}, {'C': 'C'}, {'G': 'G'}, {'F': 'F'}, {'O': 'O'}, {'P': 'P'}, {'S': 'S'}, {'R': 'R'}]

6. [{'B': 'B'}, {'C': 'C'}, {'G': 'G'}, {'F': 'F'}, {'O': 'O'}, {'P': 'P'}, {'S': 'S'}, {'R': 'R'}, {'T': 'T'}]

7. [{'B': 'B'}, {'C': 'C'}, {'U': 'U'}]

分析上述结果可以看出 第3组结果包含了第1,2组值,6包含了5,4的结果,因此可以将重复的数据进行一次过滤和筛选

第二步:

过滤重复数据,步骤一的结果可以过滤为:

3. [{'B': 'B'}, {'C': 'C'}, {'G': 'G'}, {'F': 'F'}, {'K': 'K'}, {'L': 'L'}, {'M': 'M'}]

6.[{'B': 'B'}, {'C': 'C'}, {'G': 'G'}, {'F': 'F'}, {'O': 'O'}, {'P': 'P'}, {'S': 'S'}, {'R': 'R'}, {'T': 'T'}]

7. [{'B': 'B'}, {'C': 'C'}, {'U': 'U'}]

在这一步骤中,如果不同深度的叶子节点名称重复,则按照深度,依次在key后面加入序号1,2...

即如果有3个B,则解析结果为:

[{'B': 'B'}, {'B1': 'B1'}, {'B2: 'B2'}, {'F': 'F'}, {'K': 'K'}, {'L': 'L'}, {'M': 'M'}]

第三步:

根据想要获取的KEY,在第二步获取到的list中取出对应的value,KEY以列表的方式给出

keys = [key1,key2,key3....],如要获取[B,C,F,G]

则最终结果为:

[{'B': 'B'}, {'C': 'C'}, {'G': 'G'}, {'F': 'F'}]

这样就轻而易举的解析处理我们想要的结果!

代码 -python算法实现

def responseComplexJson2LD(responseJson,assertKeys):
    '''
    :param responseJson: 类型:JSONObject 接口返回:JSON 格式
    :param assertKeys: 类型:list 需要校验的key ['resultCode','resultDesc','id','name','caption','id1','notes','name1','caption1','cubeName','functions']
                       说明:如果key里有重复的解析为 key1,key2...
    :return: list[dict]
    '''
    try:
        if not isinstance(responseJson,dict):
            raise Exception('responseJson 类型错误,必须为JSON格式')

        result_list = []

        def isInclude(dict1, dict2):
            '''
            :function: 判断 dict1 是否包含于dict2
            :param dict1:
            :param dict2:
            :return: True False
            '''
            for key1 in dict1:
                if key1 in dict2:
                    pass
                else:
                    return False
            return True

        def parse_dict(responseJson, parent):
            '''
            :递归解析json,将json树解析成list[dict,dict....]
            :param responseJson:
            :param parent:
            :return:
            '''
            if isinstance(responseJson, dict):
                dp = parent[:]
                not_l_d = [responseKey for responseKey in responseJson if not isinstance(responseJson[responseKey], (dict, list))]
                for i in not_l_d:
                    dp.append({i: responseJson[i]})
                dp_1 = dp[:]
                result_list.append(dp)
                for responseKey in responseJson:
                    if isinstance(responseJson[responseKey], (dict, list)):
                        parse_dict(responseJson[responseKey], dp_1)
            elif isinstance(responseJson, list):
                for i in responseJson:
                    dd = parent[:]
                    if not isinstance(i, (dict, list)):
                        dd.append(i)
                        result_list.append(dd)
                    else:
                        parse_dict(i, dd)
            else:
                dx = parent[:]
                dx.append(responseJson)
                result_list.append(dx)


        parse_dict(responseJson, parent=[])
        if not result_list:
            return []
        '''获取给定的assertKeys'''
        last_result =[]
        templist ={}
        if assertKeys:
            if isinstance(assertKeys,list):
                    for iter in result_list:
                        for key in iter:
                            thiskey = list(key.keys())[0]
                            '''如果有重复的key,解析为key1,key2...'''
                            i = 1
                            temp = thiskey
                            while True:
                                if thiskey in list(templist.keys()):
                                    thiskey = thiskey + str(i)
                                    i = i + 1
                                else:
                                    templist.setdefault(thiskey,key.get(temp))
                                    break
                        last_result.append(templist)
                        templist={}
            else:
                raise Exception("assertKeys 类型错误,必须为list")

        '''将assertKeys里的定义的key加入dict,不在assertKeys 定义的除去'''
        assert_result = []
        templist = {}
        for meta in last_result:
            keys = list(meta.keys())
            if isInclude(assertKeys,keys):
                for key in assertKeys:
                    templist.setdefault(key,meta.get(key))
                    assert_result.append(templist)
                templist={}
            else:
                pass

        '''对返回的last_result中的元素去重'''
        result = []
        for meta in assert_result:
            if meta not in result:
                result.append(meta)
            else:
                pass
        return result
    except Exception as err:
        Log.error(err)
        return []

结果验证

以图-1中的json 为例测试

测试1:

keylist = ['resultCode']

responseComplexJson2LD(test,keylist)

返回:

{'resultCode': 100}

测试2:

keylist = ['resultCode','resultDesc']

responseComplexJson2LD(test,keylist)

返回:

{'resultCode': 100, 'resultDesc': '成功'}

测试3:

keylist = ['resultCode','resultDesc','id']

responseComplexJson2LD(test,keylist)

返回:

{'resultCode': 100, 'resultDesc': '成功', 'id': 1}

{'resultCode': 100, 'resultDesc': '成功', 'id': 2}

测试4:--重复id,第二用id1标识

keylist = ['resultCode', 'resultDesc', 'id','id1']

responseComplexJson2LD(test,keylist)

返回:

{'resultCode': 100, 'resultDesc': '成功', 'id': 1, 'id1': 1}

{'resultCode': 100, 'resultDesc': '成功', 'id': 1, 'id1': 2}

{'resultCode': 100, 'resultDesc': '成功', 'id': 1, 'id1': 20}

{'resultCode': 100, 'resultDesc': '成功', 'id': 1, 'id1': 3}

{'resultCode': 100, 'resultDesc': '成功', 'id': 1, 'id1': 4}

{'resultCode': 100, 'resultDesc': '成功', 'id': 1, 'id1': 5}

{'resultCode': 100, 'resultDesc': '成功', 'id': 1, 'id1': 6}

{'resultCode': 100, 'resultDesc': '成功', 'id': 1, 'id1': 21}

{'resultCode': 100, 'resultDesc': '成功', 'id': 1, 'id1': 22}

{'resultCode': 100, 'resultDesc': '成功', 'id': 1, 'id1': 23}

{'resultCode': 100, 'resultDesc': '成功', 'id': 2, 'id1': 7}

{'resultCode': 100, 'resultDesc': '成功', 'id': 2, 'id1': 8}

{'resultCode': 100, 'resultDesc': '成功', 'id': 2, 'id1': 9}

{'resultCode': 100, 'resultDesc': '成功', 'id': 2, 'id1': 10}

{'resultCode': 100, 'resultDesc': '成功', 'id': 2, 'id1': 24}

{'resultCode': 100, 'resultDesc': '成功', 'id': 2, 'id1': 25}

测试5:取第二个id

keylist = ['resultCode', 'resultDesc','id1']

responseComplexJson2LD(test,keylist)

返回:

{'resultCode': 100, 'resultDesc': '成功', 'id1': 1}

{'resultCode': 100, 'resultDesc': '成功', 'id1': 2}

{'resultCode': 100, 'resultDesc': '成功', 'id1': 20}

{'resultCode': 100, 'resultDesc': '成功', 'id1': 3}

{'resultCode': 100, 'resultDesc': '成功', 'id1': 4}

{'resultCode': 100, 'resultDesc': '成功', 'id1': 5}

{'resultCode': 100, 'resultDesc': '成功', 'id1': 6}

{'resultCode': 100, 'resultDesc': '成功', 'id1': 21}

{'resultCode': 100, 'resultDesc': '成功', 'id1': 22}

{'resultCode': 100, 'resultDesc': '成功', 'id1': 23}

{'resultCode': 100, 'resultDesc': '成功', 'id1': 7}

{'resultCode': 100, 'resultDesc': '成功', 'id1': 8}

{'resultCode': 100, 'resultDesc': '成功', 'id1': 9}

{'resultCode': 100, 'resultDesc': '成功', 'id1': 10}

{'resultCode': 100, 'resultDesc': '成功', 'id1': 24}

{'resultCode': 100, 'resultDesc': '成功', 'id1': 25}

测试6:只取caption 字段,俩caption 时第二个写caption1

keylist = ['caption','caption1]

responseComplexJson2LD(test,keylist)

返回:

{'caption': '交易', 'caption1': '交易金额'}

{'caption': '交易', 'caption1': '交易用户数'}

{'caption': '交易', 'caption1': '人均交易金额'}

{'caption': '交易', 'caption1': '订单数'}

{'caption': '交易', 'caption1': '订单金额'}

{'caption': '交易', 'caption1': '大订单数'}

{'caption': '交易', 'caption1': '大订单金额'}

{'caption': '交易', 'caption1': '平均订单金额'}

{'caption': '交易', 'caption1': '大订单数占比'}

{'caption': '交易', 'caption1': '平均大订单金额'}

{'caption': '盈亏', 'caption1': '盈利用户数'}

{'caption': '盈亏', 'caption1': '亏损用户数'}

{'caption': '盈亏', 'caption1': '净盈利值'}

{'caption': '盈亏', 'caption1': '净亏损值'}

{'caption': '盈亏', 'caption1': '盈利用户数占比'}

{'caption': '盈亏', 'caption1': '亏损用户数占比'}

附上图-1完整的Json报文:

data = {
    "resultCode": 100,
    "resultDesc": "成功",
    "data": "",
    "dataList": [
        {
            "id": 1,
            "subjectId": "",
            "name": "trading",
            "caption": "交易",
            "notes": "1.人均交易金额=用户累积交易金额/交易用户数<br>2.平均订单金额=用户累计订单金额/订单数<br>3.大订单:单笔手续费大于50元的订单(手续费=每笔订单金额*万分之八)<br>4.大订单数占比=大订单数/订单数<br>5.平均大订单金额=累计大订单金额/大订单数<br>",
            "items": [
                {
                    "id": 1,
                    "name": "pay_money",
                    "caption": "交易金额",
                    "cubeName": "TRANSACTION_ANALYSIS_V3",
                    "columns": "CONTQTY",
                    "itemType": "SINGLE",
                    "calType": "",
                    "valCalType": "MONEY",
                    "functions": "SUM"
                },
                {
                    "id": 2,
                    "name": "pay_user_num",
                    "caption": "交易用户数",
                    "cubeName": "TRANSACTION_ANALYSIS_V3",
                    "columns": "ACCOUNT_ID",
                    "itemType": "SINGLE",
                    "calType": "",
                    "valCalType": "COMMON",
                    "functions": "COUNT_DISTINCT"
                },
                {
                    "id": 20,
                    "name": "pay_money,pay_user_num",
                    "caption": "人均交易金额",
                    "cubeName": "TRANSACTION_ANALYSIS_V3",
                    "columns": "CONTQTY,ACCOUNT_ID",
                    "itemType": "COMBINE",
                    "calType": "/",
                    "valCalType": "MONEY",
                    "functions": "SUM,COUNT_DISTINCT"
                },
                {
                    "id": 3,
                    "name": "order_num",
                    "caption": "订单数",
                    "cubeName": "TRANSACTION_ANALYSIS_V3",
                    "columns": "*",
                    "itemType": "SINGLE",
                    "calType": "",
                    "valCalType": "COMMON",
                    "functions": "COUNT"
                },
                {
                    "id": 4,
                    "name": "order_money",
                    "caption": "订单金额",
                    "cubeName": "TRANSACTION_ANALYSIS_V3",
                    "columns": "CONTQTY",
                    "itemType": "SINGLE",
                    "calType": "",
                    "valCalType": "MONEY",
                    "functions": "SUM"
                },
                {
                    "id": 5,
                    "name": "big_order_num",
                    "caption": "大订单数",
                    "cubeName": "TRANSACTION_ANALYSIS_V3",
                    "columns": "*",
                    "itemType": "CONDITION",
                    "calType": "",
                    "valCalType": "COMMON",
                    "functions": "COUNT"
                },
                {
                    "id": 6,
                    "name": "big_order_money",
                    "caption": "大订单金额",
                    "cubeName": "TRANSACTION_ANALYSIS_V3",
                    "columns": "CONTQTY",
                    "itemType": "CONDITION",
                    "calType": "",
                    "valCalType": "MONEY",
                    "functions": "SUM"
                },
                {
                    "id": 21,
                    "name": "order_money,order_num",
                    "caption": "平均订单金额",
                    "cubeName": "TRANSACTION_ANALYSIS_V3",
                    "columns": "CONTQTY,*",
                    "itemType": "COMBINE_CONDITION",
                    "calType": "/",
                    "valCalType": "MONEY",
                    "functions": "SUM,COUNT"
                },
                {
                    "id": 22,
                    "name": "big_order_num,order_num",
                    "caption": "大订单数占比",
                    "cubeName": "TRANSACTION_ANALYSIS_V3",
                    "columns": "*,*",
                    "itemType": "COMBINE_CONDITION",
                    "calType": "/",
                    "valCalType": "PERCENT",
                    "functions": "COUNT,COUNT"
                },
                {
                    "id": 23,
                    "name": "big_order_money,big_order_num",
                    "caption": "平均大订单金额",
                    "cubeName": "TRANSACTION_ANALYSIS_V3",
                    "columns": "CONTQTY,*",
                    "itemType": "COMBINE_CONDITION",
                    "calType": "/",
                    "valCalType": "MONEY",
                    "functions": "SUM,COUNT"
                }
            ]
        },
        {
            "id": 2,
            "subjectId": "",
            "name": "profit&loss",
            "caption": "盈亏",
            "notes": "<br>1.盈利用户数:当日用户中,净利润&gt;0的用户数<br>2.亏损用户数:当日用户中,净利润&lt;0的用户数<br>3.盈利用户占比=当日盈利用户数/当日总用户数<br>4.亏损用户占比=当日亏损用户数/当日总用户数<br>5.净盈利值:当日用户中,净利润&gt;0的值求和<br>6.净亏损值:当日用户中,净利润&lt;0的值求和<br>7.日净利润=当日净资产-前一日净资产-当日入金+当日出金<br>8.月净利润=月末净资产-月初净资产-月内总入金+月内总出金<br>其他时间粒度计算方法类似月粒度<br>",
            "items": [
                {
                    "id": 7,
                    "name": "profit_user_count",
                    "caption": "盈利用户数",
                    "cubeName": "NET_PROFIT_ANALYSIS_V3",
                    "columns": "ACCOUNT_ID",
                    "itemType": "CONDITION",
                    "calType": "",
                    "valCalType": "COMMON",
                    "functions": "COUNT_DISTINCT"
                },
                {

                    "id": 8,
                    "name": "loss_user_count",
                    "caption": "亏损用户数",
                    "cubeName": "NET_PROFIT_ANALYSIS_V3",
                    "columns": "ACCOUNT_ID",
                    "itemType": "CONDITION",
                    "calType": "",
                    "valCalType": "COMMON",
                    "functions": "COUNT_DISTINCT"
                },
                {
                    "id": 9,
                    "name": "net_profit_num",
                    "caption": "净盈利值",
                    "cubeName": "NET_PROFIT_ANALYSIS_V3",
                    "columns": "NETPROFIT",
                    "itemType": "CONDITION",
                    "calType": "",
                    "valCalType": "MONEY",
                    "functions": "SUM"
                },
                {
                    "id": 10,
                    "name": "net_loss_num",
                    "caption": "净亏损值",
                    "cubeName": "NET_PROFIT_ANALYSIS_V3",
                    "columns": "NETPROFIT",
                    "itemType": "CONDITION",
                    "calType": "",
                    "valCalType": "MONEY",
                    "functions": "SUM"
                },
                {
                    "id": 24,
                    "name": "profit_user_count,profit_total_user_count",
                    "caption": "盈利用户数占比",
                    "cubeName": "NET_PROFIT_ANALYSIS_V3",
                    "columns": "ACCOUNT_ID,ACCOUNT_ID",
                    "itemType": "COMBINE_CONDITION",
                    "calType": "/",
                    "valCalType": "PERCENT",
                    "functions": "COUNT_DISTINCT,COUNT_DISTINCT"
                },
                {
                    "id": 25,
                    "name": "loss_user_count,profit_total_user_count",
                    "caption": "亏损用户数占比",
                    "cubeName": "NET_PROFIT_ANALYSIS_V3",
                    "columns": "ACCOUNT_ID,ACCOUNT_ID",
                    "itemType": "COMBINE_CONDITION",
                    "calType": "/",
                    "valCalType": "PERCENT",
                    "functions": "COUNT_DISTINCT,COUNT_DISTINCT"
                }
            ]
        }
    ]
    }
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 211,265评论 6 490
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,078评论 2 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 156,852评论 0 347
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,408评论 1 283
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,445评论 5 384
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,772评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,921评论 3 406
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,688评论 0 266
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,130评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,467评论 2 325
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,617评论 1 340
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,276评论 4 329
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,882评论 3 312
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,740评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,967评论 1 265
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,315评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,486评论 2 348

推荐阅读更多精彩内容

  • Spring Cloud为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理,服务发现,断路器,智...
    卡卡罗2017阅读 134,629评论 18 139
  • 文章来自:http://blog.csdn.net/mj813/article/details/52451355 ...
    好大一只鹏阅读 9,189评论 2 126
  • 发现 关注 消息 iOS 第三方库、插件、知名博客总结 作者大灰狼的小绵羊哥哥关注 2017.06.26 09:4...
    肇东周阅读 12,059评论 4 62
  • 如果说儿童教育类的书籍,我只能选读一本,我会选择《孩子 挑战》这本书,这本书是美国儿童心理学鲁道夫的代表作,201...
    麻麻爸爸阅读 993评论 0 5