摘要: 通过使用Python编写一个解析Json结构对比的小工具,来提炼编程求解的通用步骤和技巧。

难度: 初级

  先上代码。

  jsondiff.py  

#!/usr/bin/python
#_*_encoding:utf-8_*_ import argparse import json
import sys reload(sys)
sys.setdefaultencoding('utf-8') def parseArgs():
description = 'This program is used to output the differences of keys of two json data.'
parser = argparse.ArgumentParser(description=description)
parser.add_argument('file', help='Given file containing two json data separated by a new line with three semicolons.')
args = parser.parse_args()
filename = args.file
return filename def readFile(filename):
content = ''
f = open(filename)
for line in f:
content += line.strip("\n")
f.close()
return content def parseKeys(jsonobj):
jsonkeys = list()
addJsonKeys(jsonobj, jsonkeys, '')
return jsonkeys def addJsonKeys(jsonobj, keys, prefix_key):
if prefix_key != '':
prefix_key = prefix_key+'.'
if isinstance(jsonobj, list):
addKeysIflist(jsonobj, keys, prefix_key)
elif isinstance(jsonobj, dict):
addKeysIfdict(jsonobj, keys, prefix_key) def addKeysIflist(jsonlist, keys, prefix_key):
if len(jsonlist) > 0:
addJsonKeys(jsonlist[0], keys, prefix_key) def addKeysIfdict(jsonobj, keys, prefix_key):
for (key, value) in jsonobj.iteritems():
keys.append(prefix_key + key)
addJsonKeys(value, keys, prefix_key+key) def diffKeys(json1, json2):
keys1 = parseKeys(json1)
keys2 = parseKeys(json2)
keyset1 = set(keys1)
keyset2 = set(keys2)
return keyset1.difference(keyset2) def cmpArray(jsonArr1, jsonArr2, diff, prefix_key):
'''
need to be improved
'''
arrlen1 = len(jsonArr1)
arrlen2 = len(jsonArr2)
minlen = min(arrlen1, arrlen2)
if arrlen1 != arrlen2:
diff.append((prefix_key+'.length', arrlen1, arrlen2))
for i in range(0, minlen):
diffDict(jsonArr1[i], jsonArr2[i], diff, prefix_key) def cmpPrimitive(key, value1, value2, diff, prefix_key): if isinstance(value1,list) or isinstance(value1, dict) \
or isinstance(value2, list) or isinstance(value2, dict):
return if value1 != value2:
diff.append((prefix_key + key, str(value1), str(value2))) def diffDict(json1, json2, diff, prefix_key): if prefix_key != '':
prefix_key = prefix_key+'.' for (key, value) in json1.iteritems():
json2Value = json2.get(key)
#print "key: ", key, ", value: ", value, " , value2: ", json2Value if json2Value == None:
diff.append((prefix_key + key, value, None)) if isinstance(value, dict) and isinstance(json2Value, dict):
diffDict(value, json2Value, diff, prefix_key + key) if isinstance(value, list) and isinstance(json2Value, list):
cmpArray(value, json2Value, diff, prefix_key + key) cmpPrimitive(key, value, json2Value, diff, prefix_key) def diffJson(json1, json2):
jsondiff = list()
diffDict(json1, json2, jsondiff, '')
return jsondiff def diffJsonToFile(filename, json1, json2):
f_res = open(filename, 'w')
diff_res = diffJson(json1, json2)
for diff in diff_res:
(key,v1,v2) = diff
if v2 is None:
f_res.write('key %s in json1 not in json2. \n' % key)
else:
f_res.write('key %s in json1 = %s yet in json2 = %s. \n' %(key, v1, v2)) f_res.close() def tesParsetKeysSingle(jsonobj, expected):
assert set(parseKeys(jsonobj)) == set(expected) def testParseKeys():
for v in ({}, [], "good", 1, 3.14, -2.71, -1, 0.1, 2.71E3, 2.71E+3, 2.71E-32, 2.71e3, 2.71e+3, 2.71e-32, True, False, None, "null\n\\\"\/\b\f\n\r\t\u"):
tesParsetKeysSingle(parseKeys(v), [])
tesParsetKeysSingle({"code": 200}, ['code'])
tesParsetKeysSingle({"code": 200, "msg": "ok", "list": [], "extra":{}}, ['code', 'msg', 'list', 'extra'])
tesParsetKeysSingle({"code": 200, "msg": "ok", "list": [{"id": 20, "no":""}], "extra":{"size": 20, "info": {"owner": "qin"}}}, ['code', 'msg', 'list', 'list..id', 'list..no', 'extra', 'extra.size', 'extra.info', 'extra.info.owner'])
tesParsetKeysSingle({'msg': 'ok', 'code': 200, 'list': [{'items': [{'price': 21, 'infos': {'feature': ''}, 'name': 'n1'}], 'id': 20, 'no': ''}], 'metainfo': {'total': 20, 'info': {'owner': 'qinshu', 'parts': [{'count': 13, 'time': {'start': 1230002456, 'end': 234001234}}]}}}, ['msg', 'code', 'list', 'list..items', 'list..items..price', 'list..items..infos', 'list..items..infos.feature', 'list..items..name','list..id', 'list..no', 'metainfo', 'metainfo.total', 'metainfo.info', 'metainfo.info.owner', 'metainfo.info.parts', 'metainfo.info.parts..count', 'metainfo.info.parts..time' ,'metainfo.info.parts..time.start', 'metainfo.info.parts..time.end']) print 'testPassed' def test():
testParseKeys() if __name__ == "__main__": test() filename = parseArgs()
content = readFile(filename)
jsondataArr = content.split(';;;')
content1 = jsondataArr[0]
content2 = jsondataArr[1]
json1 = json.loads(content1)
json2 = json.loads(content2) print "keys in json_data_v2: "
print parseKeys(json2) print 'keys in json_data_v1 yet not in json_data_v2: '
print diffKeys(json1, json2) print 'keys in json_data_v2 yet not in json_data_v1: '
print diffKeys(json2, json1)

Json 测试数据:

{
"code": 200,
"msg": "ok",
"list": [
{
"id": 20,
"no": "1000020",
"items": [
{
"name": "n1",
"price": 21,
"infos": {
"feature": ""
}
}
]
}
],
"metainfo": {
"total": 20,
"info": {
"owner": "qinshu",
"parts": [
{
"count": 13,
"time": {
"start": 1230002456,
"end": 234001234
}
}
]
}
}
} ;;; {
"code": 200,
"msg": "ok",
"success": true,
"list": [
{
"id": 22,
"no": "1000020",
"items": [
{
"name": "n1",
"price": 21,
"comment": "very nice",
"infos": {
"feature": ""
}
},
{
"name": "n2",
"price": 22,
"comment": "good",
"infos": {
"feature": "small"
}
}
]
}
],
"metainfo": {
"total": 20,
"info": {
"owner": "qinshu",
"parts": [
{
"count": 15,
"range": {
"start": 1230003456,
"end": 234007890
}
}
]
}
}
}

 使用:

  将要比较的两个 json 串拷贝到一个文本文件 json_data.txt 里,并使用一个 ;;; 的行隔开; 然后运行 python jsondiff.py json_data.txt

目前主要是能够比较 json 的结构, 即输出 json 串相异的 key 的集合。

  编程求解问题

  确定问题与求解方向  ->  结构解析与递归  ->  算法设计  ->  编程与测试  ->  总结

  确定问题与求解方向 

  首先确定一个合适的问题, 一个合适的求解方向。在 json 串对比的问题域中, 可以有两个目标: 1.  比较两个 json 串的结构的不同; 常常用于 API 变更后的兼容; 2.  比较两个 json 串的结构及值的差异。 第二个目标由于有数组的存在,而变得比较复杂。 鉴于目标一比较常用,可以先实现。

  结构解析与递归

  其次,要确定处理问题所涉及的对象结构。要解析复杂的结构, 通常也会涉及到递归求解。可以使用递归求解的问题特征是: 1.  对象结构是一个组合结构,该结构可以通过一个原子量与一个更小的同型结构组合而成; 2.  问题的解结构可以通过原子量的解结构与更小规模的同型结构的解结构组合而成; 3.  原子量的解是可行的。

  几乎所有常用的数据结构都是递归的。一个数值可以分解为两个数值之和; 一个字符串可以分解为一个字符与一个子字符串的连接;一个列表、链表、栈、队列均可以由列首或列尾元素与剩余元素组合而成; 一棵树可以通过根节点与其左右子树组合而成;一个图可以通过其分割的子图构成。无处不可递归。不过递归需要注意的一点是: 在子问题的解结构组合成原问题的解结构的时候,最好不存在解结构之间的顺序关系。也就是说,原问题的解结构是一个无序集合,只要子问题的解结构也是无序集合,那么就尽可以将子问题的解集合添加到原问题的解结构中;如果存在顺序关系,则在算法设计中要尤其注意确保这种顺序。

  算法设计

  理解了所要处理的结构,就可以进行算法设计了。 JSON 串有很明显的递归特性, 因此适合用递归来求解。Json 结构定义参见 http://www.json.org/ 。对于 Json 串的处理,可以分为三种情况: (1) 原子量的处理,比如数值、字符串、布尔值; (2)  映射的处理,遍历每个 key-value 对, 如果 value 是映射,那么就递归使用(2);如果 value 是数组,则使用 (3);  3. 列表的处理, 遍历每一个元素,若元素是映射,则使用(2) 处理;若元素是数组,则使用(3)处理。具体见程序。

  编程测试

  设计好算法,就可以开始愉快地编程啦! 编程可以使用意图导航编程, 首先编写出几个空函数, 表达对问题求解的步骤,然后完善每个函数,必要时修改其接口定义。 编程完成后需要使用覆盖性的测试来尽早检测出 bug ,  修复程序隐藏的错误, 提高程序的质量。

  话说,富有经验的程序员会花费更多时间在算法设计上,确保其可扩展性和完善性;算法设计也是更考验程序员的思维能力,无需电脑就可进行; 而编程则是一种更实际的乐趣。

  一点技巧

  在递归求解中, 如何构造最终的解结构是个问题。一个较简单的办法是,构造一个空列表,然后在递归的过程中,在空列表中添加子解。通常有一个主递归函数, 比如程序中的  addJsonKeys , 用于控制子结构的流程跳转; 而处理子结构的分函数 addKeysIflist , addKeysIfdict 可递归跳转到该主函数。在主递归函数最外层有一个调用者,用于设置主递归函数的初始值,比如空列表的解结构、其他的初始值。

总结记录

  总结与记录也是必不可少的。回顾一下,在完成该问题的求解过程中,遇到了什么问题, 收获了怎样的技法呢? 无论多小都值得记录,积微至著;尤其是一些不引人注意的"编程微思想"。其实只要是编程问题,核心总是"数据结构+算法"。 即使在应用编程中, 其实也是"数据结构+算法"的引申。"数据结构" 变成了应用中的 "数据库+缓存", "算法" 变成了 "流程+规则",所做的需求开发,也就是在 "数据库+缓存" 的数据背景下,设计和规划 "流程和规则", 以适应产品和业务的发展需求。

Python实现Json结构对比的小工具兼谈编程求解问题的更多相关文章

  1. 一个Json结构对比的Python小工具兼谈编程求解问题

    先上代码. jsondiff.py #!/usr/bin/python #_*_encoding:utf-8_*_ import argparse import json import sys rel ...

  2. Python 使用 PyQt5 开发的关机小工具

    前两天简单认识了一下PyQt5,通过练习开发了一款在Window下自定义关机的小工具,代码如下 import os,sys,time from PyQt5 import QtCore,QtWidget ...

  3. javascript json转为 go struct 小工具代码

    /** * Created by cdpmac on 15/10/20. */ var topname="Ap"; var jdata={ "item": { ...

  4. 给json格式化的一个小工具

    var glob = require("glob") // options is optional var fs=require("fs") glob(&quo ...

  5. 几个可以提高工作效率的Python内置小工具

    在这篇文章里,我们将会介绍4个Python解释器自身提供的小工具.这些小工具在笔者的日常工作中经常用到,减少了各种时间的浪费,然而,却很容易被大家忽略.每当有新来的同事看到我这么使用时,都忍不住感叹, ...

  6. JSON生成c#类代码小工具

    JSON生成c#类代码小工具 为什么写这么个玩意 最近的项目中需要和一个服务端程序通讯,而通讯的协议是基于流行的json,由于是.net,所以很简单的从公司代码库里找到了Newtonsoft.dll( ...

  7. 用 Python 制作一个艺术签名小工具,给自己设计一个优雅的签名

    生活中有很多场景都需要我们签字(签名),如果是一些不重要的场景,我们的签名好坏基本无所谓了,但如果是一些比较重要的场景,如果我们的签名比较差的话,就有可能给别人留下不太好的印象了,俗话说字如其人嘛,本 ...

  8. 利用 Python 写一个颜值测试小工具

    我们知道现在有一些利用照片来测试颜值的网站或软件,其实使用 Python 就可以实现这一功能,本文我们使用 Python 来写一个颜值测试小工具. 很多人学习python,不知道从何学起.很多人学习p ...

  9. 用Python写一个向数据库填充数据的小工具

    一. 背景 公司又要做一个新项目,是一个合作型项目,我们公司出web展示服务,合作伙伴线下提供展示数据. 而且本次项目是数据统计展示为主要功能,并没有研发对应的数据接入接口,所有展示数据源均来自数据库 ...

随机推荐

  1. [实战]MVC5+EF6+MySql企业网盘实战(1)

    写在前面 不久前,一个朋友让帮他弄一个单位的企业网盘的管理站点,一直忙,最近抽出了点时间,也想琢磨琢磨mvc,ef,mysql,这算是边琢磨,边实践吧. 系列文章 [实战]MVC5+EF6+MySql ...

  2. Python面向对象之属性

    属性的定义和调用 1,定义时,在普通方法的基础上添加@property装饰器 2,定义时,属性仅有一个self参数 3,调用时,无需括号 vim day7-8.py #!/usr/bin/python ...

  3. poj3168 Barn Expansion【计算几何 平面扫描】

    Farmer John has N (1 <= N <= 25,000) rectangular barns on his farm, all with sides parallel to ...

  4. java 内存深度解析

    java 中的包括以下几大种的内存区域:1.寄存器    2.stack(栈) 3.heap(堆) 4.数据段 5.常量池 那么相应的内存区域中又存放什么东西(主要介绍 stack heap)? 栈: ...

  5. POJ 3368 & UVA 11235 - Frequent values

    题目链接:http://poj.org/problem?id=3368 RMQ应用题. 解题思路参考:http://blog.csdn.net/libin56842/article/details/4 ...

  6. oozie学习笔记

    #################################################################################################### ...

  7. Python:zip()函数

    zip()函数的定义 从参数中的多个迭代器取元素组合成一个新的迭代器: 返回:返回一个zip对象,其内部元素为元组:可以转化为列表或元组: 传入参数:元组.列表.字典等迭代器. zip()函数的用法 ...

  8. Mac操作技巧

    Command+Option+P+R,重置PRAM的. 官方关于重置PRAM的说明.(有助于电脑提速) 安装新版系统的时候失败,原因是下载的镜像有问题版本不对,具体是中国区暂未更新镜像,下载下来的有问 ...

  9. GCD之各种派发

    dispatch_apply的用法 并行模拟for循环,将指定的代码循环10次,一般会把这些代码附加到一个queue上,然后在 dispatch_apply里并行 dispatch_queue_t q ...

  10. 牛客练习赛18E pocky游戏 状压dp

    正解:状压dp+辅助dp 解题报告: 来还债辣!NOIp之后还是轻松很多了呢,可以一点点儿落实之前欠下的各种东西一点点提升自己!加油鸭! 是个好题,可以积累套路,启发性强,而且很难 哦而且状压它也是个 ...