Python实现Json结构对比的小工具兼谈编程求解问题
摘要: 通过使用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结构对比的小工具兼谈编程求解问题的更多相关文章
- 一个Json结构对比的Python小工具兼谈编程求解问题
先上代码. jsondiff.py #!/usr/bin/python #_*_encoding:utf-8_*_ import argparse import json import sys rel ...
- Python 使用 PyQt5 开发的关机小工具
前两天简单认识了一下PyQt5,通过练习开发了一款在Window下自定义关机的小工具,代码如下 import os,sys,time from PyQt5 import QtCore,QtWidget ...
- javascript json转为 go struct 小工具代码
/** * Created by cdpmac on 15/10/20. */ var topname="Ap"; var jdata={ "item": { ...
- 给json格式化的一个小工具
var glob = require("glob") // options is optional var fs=require("fs") glob(&quo ...
- 几个可以提高工作效率的Python内置小工具
在这篇文章里,我们将会介绍4个Python解释器自身提供的小工具.这些小工具在笔者的日常工作中经常用到,减少了各种时间的浪费,然而,却很容易被大家忽略.每当有新来的同事看到我这么使用时,都忍不住感叹, ...
- JSON生成c#类代码小工具
JSON生成c#类代码小工具 为什么写这么个玩意 最近的项目中需要和一个服务端程序通讯,而通讯的协议是基于流行的json,由于是.net,所以很简单的从公司代码库里找到了Newtonsoft.dll( ...
- 用 Python 制作一个艺术签名小工具,给自己设计一个优雅的签名
生活中有很多场景都需要我们签字(签名),如果是一些不重要的场景,我们的签名好坏基本无所谓了,但如果是一些比较重要的场景,如果我们的签名比较差的话,就有可能给别人留下不太好的印象了,俗话说字如其人嘛,本 ...
- 利用 Python 写一个颜值测试小工具
我们知道现在有一些利用照片来测试颜值的网站或软件,其实使用 Python 就可以实现这一功能,本文我们使用 Python 来写一个颜值测试小工具. 很多人学习python,不知道从何学起.很多人学习p ...
- 用Python写一个向数据库填充数据的小工具
一. 背景 公司又要做一个新项目,是一个合作型项目,我们公司出web展示服务,合作伙伴线下提供展示数据. 而且本次项目是数据统计展示为主要功能,并没有研发对应的数据接入接口,所有展示数据源均来自数据库 ...
随机推荐
- 静态时序分析基础STA
静态时序分析SAT 1. 背景 静态时序分析的前提就是设计者先提出要求,然后时序分析工具才会根据特定的时序模型进行分析,给出正确是时序报告. 进行静态时序分析,主要目的就是为了提高系统工作主频 ...
- 【CF884D】Boxes And Balls 哈夫曼树
[CF884D]Boxes And Balls 题意:有n个箱子和若干个球,球的颜色也是1-n,有ai个球颜色为i,一开始所有的球都在1号箱子里,你每次可以进行如下操作: 选择1个箱子,将里面所有的球 ...
- UEditor富文本WEB编辑器自定义默认值设置方法
1.在使用UEditor编辑器编写内容时你会发现,当输入的内容较多时,编辑框的边框高度也会自动增加,若希望输入内容较多时以拉框滚动的效果. 方法:找到Ueditor文件根目录下的ueditor.con ...
- poj2492 A Bug's Life【并查集】
Background Professor Hopper is researching the sexual behavior of a rare species of bugs. He assume ...
- js里常用函数之高阶函数
高阶函数:将函数作为参数或者返回值的函数.将函数作为参数的用法通常称作回调函数,函数参数通常会在主函数被执行之后被高阶函数调用. 高阶函数的使用实例.可以把有相似操作的函数用一个高阶函数来重构,精简代 ...
- Python面试数据库
1.列举常见的关系型数据库和非关系型都有那些? 关系型 : MySQL,SQL Server ,Oracle , Sybase, DB2 非关系型 : Redis, MongodDB 2.MySQL常 ...
- sklearn学习总结(超全面)
https://blog.csdn.net/fuqiuai/article/details/79495865 前言sklearn想必不用我多介绍了,一句话,她是机器学习领域中最知名的python模块之 ...
- CentOS 7设置Samba共享目录
1. 安装Samba服务 yum -y install samba # 查看yum源中Samba版本 yum list | grep samba # 查看samba的安装情况 rpm -qa | gr ...
- 【python-opencv】17-形态学操作-腐蚀与膨胀
形态学操作其实就是改变物体的形状,比如腐蚀就是"变瘦",膨胀就是"变胖",看下图就明白了: 形态学操作一般作用于二值化图(也可直接作用于原图),来连接相邻的元素 ...
- IIS/ASP.NET访问共享文件夹的可用方式
[截止2014-10-14] 网上搜索了很多篇文章,所提及的总共有两种方式: 1.Asp.Net模拟登陆: 例如: 实战ASP.NET访问共享文件夹(含详细操作步骤) 实现一个2008serve的II ...