Python学习:json对象快速访问,jsonpath的神奇妙用

概述

json对象和xml对象都是树形结构,文本存储,容易理解。但是,构造起来却相当繁琐,嵌套比较深,代码也不好看,难以维护。

xml和xpath是一对,很容易联想到json和jsonpath也应该成对出现。jsonpath可以使用得json对象访问变得更加简洁,代码更易维护,避免过多的分支嵌套。幸运的是,jsonpath有规范定义,java和python也都有第三库可以使用。

安装jsonpath库

jsonpath第三方库,github上有好几个,比如jsonpath,jsonpath-rw,jsonpath-ng等等。由于json对象的访问涉及到字段的读写,jsonpath和jsonpath-rw都还不支持。jsonpath-ng是基于jsonpath-rw扩展的,可以支持读写、过滤等,可惜帮助少得可怜,而且都是字段读居多,写的样例很难搜索得到。本文就通过jsonpath,实现读写json的字段值。

pip3.9 install jsonpath-ng

 

JosnHelper定义

get/set方法

为了操作Json对象,我们就定义一个类,用来读写json对象。

from jsonpath_ng import parse

class JsonHelper:
    def __init__(self, buffer: dict):
        self._buffer = buffer

    def get(self, field: str):
        if not field.startswith('$..'):
            condition = '$..' + field
        else:
            condition = field
        ret = []
        for match in parse(condition).find(self.__dict__['_buffer']):
            ret.append(match.value)

        if not ret:
            raise Exception("field:{} is not exist".format(field))
        else:
            return ret[0]

    def set(self, field: str, value):
        if not field.startswith('$..'):
            condition = '$..' + field
        else:
            condition = field

        parse(condition).update(self.__dict__['_buffer'], value)
        return self

JsonHelper比较简单,就是简单定义了get和set两个方法,set方法返回self,可以实现链式调用。使用样例如下:

js = {'a': 10, 'b': 20, 'c': {'e': 10, 'f': 'string'}}
print(JsonHelper(js).get('a'))
print(JsonHelper(js).get('b'))
print(JsonHelper(js).set('a', 1).set('b', 2).set('e', 30)._buffer)

输出:

10
20
{'a': 1, 'b': 2, 'c': {'e': 30, 'f': 'string'}}

下标方法

python的_getitemsetitem_复写,可以实现下标的访问,使得JsonHelper使用更进一步简化。代码如下:

class JsonHelper:
    def __getitem__(self, field):
        return self.get(field)

    def __setitem__(self, field, value):
        return self.set(field, value)

代码没什么好讲的,只是简单的调用get/set方法。使用如下:

js = {'a': 10, 'b': 20, 'c': {'e': 10, 'f': 'string'}}
print(JsonHelper(js)['a'])
print(JsonHelper(js)['b'])
JsonHelper(js)['a'] = 1
print(JsonHelper(js)['a'])

输出:

10
20
1

属性方法

python的属性可以动态添加,这个比较神奇,使得JsonHelper访问json对象简洁。实现原理,就是重载__getattr__和__setattr__两个方法:

class JsonHelper:
    def __init__(self, buffer: dict):
        self._buffer = buffer

    def __getattr__(self, field):
        if not field.startswith('$..'):
            condition = '$..' + field
        else:
            condition = field
        ret = []
        for match in parse(condition).find(self.__dict__['_buffer']):
            ret.append(match.value)

        if not ret:
            raise Exception("field:{} is not exist".format(field))

        super().__setattr__(field, ret[0])
        return self.__dict__[field]

    def __setattr__(self, field: str, value):
        super().__setattr__(field, value)
        if field == '_buffer':
            pass
        else:
            if not field.startswith('$..'):
                condition = '$..' + field
            else:
                condition = field

            if not [match for match in parse(condition).find(self.__dict__['_buffer'])]:
                raise Exception("field:{} is not exist".format(field))

            parse(condition).update(self.__dict__['_buffer'], value)

            super().__setattr__(field, value)

使用举例,如下:

js = {'a': 10, 'b': 20, 'c': {'e': 10, 'f': 'string'}}
print(JsonHelper(js).a)
print(JsonHelper(js).b)
JsonHelper(js).a = 1
print(JsonHelper(js).a)

输出:

10
20
1

实现主要困难,在于动态添加的属性值,要同步到固有的属性_buffer中,务必确保一致性,不然会出现数据一致性问题。另外,有个缺陷,动态添加的属性,内存空间又多存了一份。当然,不使用属性访问,就不会新增空间开销。

重载__getattr__等方法,容易出现死循环,重载时不能直接使用self.buffer访问固有的属性,要通过self.__dict__进行访问。

好吧,我承认,动态新增属性完全没有必要,访问不存在的属性时自动去访问_buffer即可,无需真的动态去新增属性,这样实现更加简单一些,也不会存在一致性问题(比如删除json节点的时候,不需要同步删除属性)。重新实现的代码如下:

class JsonHelper(object):
    def __init__(self, buffer: dict):
        self._buffer = buffer

    def __getattr__(self, field):
        if field in self.__dir__() or field == '_buffer':
            return super().__getattr__(field)

        return self.get(field)

    def __setattr__(self, field: str, value):
        if field in self.__dir__() or field == '_buffer':
            super().__setattr__(field, value)
            return
        else:
            self.set(field, value)

使用属性访问的时候,对json节点名称有比较高的要求,要符合python代码规范,比如节点名称"a-b"这样带下划线、点、完全是数字的,是不能作为属性名称来使用的。

总结

通过jsonpath的基本功能,就已经实现了json对象的快速访问,使得代码更加简化,也很实用,以上三种访问形式,均能实现其功能,可以根据实际需要使用哪一种。jsonpath的功能更加强大,有兴趣的同学可以访问:https://github.com/h2non/jsonpath-ng

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值