当前位置:   article > 正文

Python学习:json对象快速访问(续)_python jsonnodefield

python jsonnodefield

问题

我们再次回到jsonpath的问题,起初使用jsonpath-ng实现快速访问json对象节点。但jsonpath-ng的性能问题,导致这个方法实在是糟糕,无法使用,悲剧的是已经写了很多代码,再用全路径访问json工作量有点太大,有点进退两难。

回过头思考,起初我的目的,就是不必要写全路径访问json节点,通过jsonpath只需要写一个节点进行模糊匹配,实现节点访问。实际上该需求并不复杂,并没有用到jsonpath的复杂功能。能不能不用jsonpath,也能实现?

幸运的是方案是存在的,而且实现起来也很容易。python语言天生就支持json对象,不需要再进行对象的序列化,可以直接利用这个语言特性。

方案

节点对象获取

首先我们解决json节点获取问题。我们写下如下的代码:

 

  1. def get_one_json_node(data: dict, field: str):
  2. if isinstance(data, list):
  3. for it in data:
  4. ret = get_one_json_node(it, field)
  5. if ret is not None:
  6. return ret
  7. if not isinstance(data, dict):
  8. return None
  9. if field in data.keys():
  10. return data[field]
  11. for it in data.keys():
  12. ret = get_one_json_node(data[it], field)
  13. if ret:
  14. return ret
  15. return None
'
运行

测试代码:

 

  1. class TestJsonGet:
  2. js = {'a': 10, 'b': 20, 'c': {'e': 10, 'f': 'string'}, 'c1': {'e': {'f1': 30}, 'f': 'string'},
  3. 'c2': {'e': 10, 'f': 'string'}, 'z': [{'z1': 10}, {'z1': 20}]}
  4. def test_get1(self):
  5. assert 10 == get_one_json_node(self.js, 'a')
  6. assert 10 == get_one_json_node(self.js, 'e')
  7. def test_get2(self):
  8. assert 10 == get_one_json_node(self.js, 'c.e')
'
运行

通过pytest测试发现,test_get2测试失败。原因是有多个e节点,我们需要支持c.e的访问,于是再封装一个上层方法:

 

  1. def get_json_node(data: dict, field: str):
  2. node_path = field.split(".")
  3. node = data
  4. find = False
  5. for it in node_path:
  6. node = get_one_json_node(node, it)
  7. if not node:
  8. return None
  9. else:
  10. find = True
  11. if find:
  12. return node
  13. else:
  14. return None
'
运行

测试代码:

 

  1. class TestJsonGet:
  2. js = {'a': 10, 'b': 20, 'c': {'e': 10, 'f': 'string'}, 'c1': {'e': {'f1': 30}, 'f': 'string'},
  3. 'c2': {'e': 10, 'f': 'string'}, 'z': [{'z1': 10}, {'z1': 20}]}
  4. def test_get3(self):
  5. assert 10 == get_json_node(self.js, 'a')
  6. assert 10 == get_json_node(self.js, 'e')
  7. assert 10 == get_json_node(self.js, 'c.e')
  8. def test_get4(self):
  9. assert 10 == get_json_node(self.js, 'z.z1')
'
运行

这次pytest测试就全部通过了(实际上,我也测试了数组节点的访问。为了简单起见,匹配第一个节点时get就直接返回,无需返回所有匹配节点)。节点的get方法执行时间0.1ms左右,相比jsonpath-ng提升近百倍了,可以接受。测试结果如下:

 

  1. pytest -s test/test_jsonhelper.py::TestJsonGet
  2. ======================================================= test session starts =======================================================
  3. platform darwin -- Python 3.8.2, pytest-6.1.2, py-1.9.0, pluggy-0.13.1
  4. rootdir: /Users/xxx/data/code/python
  5. collected 4 items
  6. test/test_jsonhelper.py ..func:{get_json_node} exec time is:{0.00317} ms
  7. func:{get_json_node} exec time is:{0.08789} ms
  8. func:{get_json_node} exec time is:{0.00195} ms
  9. .func:{get_json_node} exec time is:{0.00317} ms

节点的写入

同理,对于json节点设置可以参考get方法,写两个函数,具体代码如下:

 

  1. def set_one_json_node(data: dict, field: str, value):
  2. if isinstance(data, list):
  3. for it in data:
  4. ret = set_one_json_node(it, field, value)
  5. if ret is not None:
  6. return ret
  7. if not isinstance(data, dict):
  8. return None
  9. if field in data.keys():
  10. data[field] = value
  11. return data
  12. for it in data.keys():
  13. ret = set_one_json_node(data[it], field, value)
  14. if ret:
  15. return ret
  16. return None
  17. def set_json_node(data: dict, field: str, value):
  18. pos = field.find('.')
  19. if pos != -1:
  20. parent = field[0:pos]
  21. node = get_json_node(data, parent)
  22. if node is None:
  23. return None
  24. else:
  25. return set_one_json_node(node, field[pos + 1:], value)
  26. else:
  27. return set_one_json_node(data, field, value)
'
运行

测试代码如下:

 

  1. class TestJsonSet:
  2. js = {'a': 10, 'b': 20, 'c': {'e': 10, 'f': 'string'}, 'c1': {'e': {'f1': 30}, 'f': 'string'},
  3. 'c2': {'e': 10, 'f': 'string'}, 'z': [{'z1': 10}, {'z1': 20}]}
  4. def test_set1(self):
  5. js = copy.deepcopy(self.js)
  6. set_one_json_node(js, 'a', 20)
  7. set_one_json_node(js, 'e', 30)
  8. assert 20 == get_one_json_node(js, 'a')
  9. assert 30 == get_one_json_node(js, 'e')
  10. def test_set2(self):
  11. js = copy.deepcopy(self.js)
  12. set_one_json_node(js, 'c.e', 20)
  13. assert None == get_one_json_node(self.js, 'c.e')
  14. def test_set3(self):
  15. js = copy.deepcopy(self.js)
  16. set_json_node(js, 'a', 20)
  17. set_json_node(js, 'e', 30)
  18. assert 20 == get_json_node(js, 'a')
  19. assert 30 == get_json_node(js, 'e')
  20. set_json_node(js, 'c.e', 40)
  21. assert 40 == get_json_node(js, 'c.e')
  22. def test_set4(self):
  23. js = copy.deepcopy(self.js)
  24. set_json_node(js, 'z.z1', 100)
  25. assert 100 == get_json_node(js, 'z.z1')
'
运行

pytest测试,set方法的执行时间和get方法执行时间类似,没有出现大的变动,相比jsonpath-ng也有巨大的提升,能够满足要求。运行结果如下:

 

  1. pytest -s test/test_jsonhelper.py::TestJsonSet
  2. ======================================================= test session starts =======================================================
  3. platform darwin -- Python 3.8.2, pytest-6.1.2, py-1.9.0, pluggy-0.13.1
  4. rootdir: /Users/xxx/data/code/python
  5. collected 4 items
  6. test/test_jsonhelper.py func:{test_set1} exec time is:{0.03638} ms
  7. ..func:{set_json_node} exec time is:{0.00293} ms
  8. func:{set_json_node} exec time is:{0.00293} ms
  9. func:{get_json_node} exec time is:{0.00195} ms
  10. func:{get_json_node} exec time is:{0.00195} ms
  11. func:{get_json_node} exec time is:{0.00098} ms
  12. func:{set_json_node} exec time is:{0.00513} ms
  13. func:{get_json_node} exec time is:{0.00098} ms
  14. .func:{get_json_node} exec time is:{0.00195} ms
  15. func:{set_json_node} exec time is:{0.01514} ms
  16. func:{get_json_node} exec time is:{0.02588} ms

讨论

我们简单实现了json访问的get和set方法,性能得到比较好的提升,对于频繁访问json对象,可以不使用jsonpath-ng,直接使用上面的方法,从而规避jsonpath-ng的性能问题。

有的时候,对于自身的需求,一定要多思考,也许存在一种简单的方法,就能解决。不一定杀鸡得用牛刀,而且可能是一把生锈的牛刀,盲目使用第三方库,可能会走不少弯路。

结合之前的总结,完善JsonHelper类型,这样就可以使用了(当然代码的健壮性,还有不少工作,依据实际需求,大家有兴趣自行完善即可)。辅助类型JsonHelper代码如下:

 

  1. class JsonHelper:
  2. def __init__(self, buffer: dict):
  3. self.__dict__['_buffer'] = buffer
  4. def get(self, field: str):
  5. ret = self.__get_json_node(self.__dict__['_buffer'], field)
  6. if not ret:
  7. raise Exception("field:{} is not exist".format(field))
  8. else:
  9. return ret
  10. def set(self, field: str, value):
  11. ret = self.__set_json_node(self.__dict__['_buffer'], field, value)
  12. if ret is None:
  13. raise Exception("field:{} is not exist".format(field))
  14. return self
  15. def __get_one_json_node(self, data: dict, field: str):
  16. if isinstance(data, list):
  17. for it in data:
  18. ret = self.__get_one_json_node(it, field)
  19. if ret is not None:
  20. return ret
  21. if not isinstance(data, dict):
  22. return None
  23. if field in data.keys():
  24. return data[field]
  25. for it in data.keys():
  26. ret = self.__get_one_json_node(data[it], field)
  27. if ret:
  28. return ret
  29. return None
  30. def __get_json_node(self, data: dict, field: str):
  31. node_path = field.split(".")
  32. node = data
  33. find = False
  34. for it in node_path:
  35. node = self.__get_one_json_node(node, it)
  36. if not node:
  37. return None
  38. else:
  39. find = True
  40. if find:
  41. return node
  42. else:
  43. return None
  44. def __set_one_json_node(self, data: dict, field: str, value):
  45. if isinstance(data, list):
  46. for it in data:
  47. ret = self.__set_one_json_node(it, field, value)
  48. if ret is not None:
  49. return ret
  50. if not isinstance(data, dict):
  51. return None
  52. if field in data.keys():
  53. data[field] = value
  54. return data
  55. for it in data.keys():
  56. ret = self.__set_one_json_node(data[it], field, value)
  57. if ret:
  58. return ret
  59. return None
  60. def __set_json_node(self, data: dict, field: str, value):
  61. pos = field.find('.')
  62. if pos != -1:
  63. parent = field[0:pos]
  64. node = self.__get_json_node(data, parent)
  65. if node is None:
  66. return None
  67. else:
  68. return self.__set_one_json_node(node, field[pos + 1:], value)
  69. else:
  70. return self.__set_one_json_node(data, field, value)
'
运行
声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/喵喵爱编程/article/detail/977912
推荐阅读
相关标签
  

闽ICP备14008679号