跳转到主要内容

处理JSON作为Python对象的库

项目描述

jsonobject

Build Status Downloads Supported Versions Contributors

jsonobject是一个Python库,用于处理深度嵌套的JSON对象,这些对象具有良好的Python对象模式。

jsonobject由Dimagi制作,我们在这里构建、使用并贡献开源软件,以减少世界上的不平等。

jsonobject受couchdbkit中的Document/DocumentSchema部分启发,并与它高度兼容API。由于jsonobject不仅更简单且独立,而且更快,我们还维护了couchdbkit的分支,jsonobject-couchdbkit,该分支由jsonobject支持,可以作为主库的无缝替代品。

它在CommCare HQ源代码)中得到广泛使用,API基本稳定,但更高级的功能可能会在未来更改。

入门指南

要使用pip安装,只需运行

pip install jsonobject

示例

以下代码定义了一个简单的用户模型,以及其自然映射到JSON的方式。

from jsonobject import *

class User(JsonObject):
    username = StringProperty()
    name = StringProperty()
    active = BooleanProperty(default=False)
    date_joined = DateTimeProperty()
    tags = ListProperty(unicode)

一旦定义,就可以用来包装或生成反序列化的JSON。

>>> user1 = User(
    name='John Doe',
    username='jdoe',
    date_joined=datetime.datetime.utcnow(),
    tags=['generic', 'anonymous']
)
>>> user1.to_json()
{
    'name': u'John Doe',
    'username': u'jdoe',
    'active': False,
    'date_joined': '2013-08-05T02:46:58Z',
    'tags': [u'generic', u'anonymous']
}

注意,在JSON中,日期时间被转换为ISO格式的字符串,但在对象上是真实的日期时间。

>>> user1.date_joined
datetime.datetime(2013, 8, 5, 2, 46, 58)

JsonObject构造函数

上面定义的JsonObject子类User具有许多内置功能。基本操作包括

  1. 从反序列化的JSON(例如json.loads的输出)创建新对象
  2. 使用给定值构造新对象
  3. 修改对象
  4. 转换为反序列化的JSON(例如json.dumps的输入)

1和2是通过构造函数实现的。调用构造函数主要有两种方式

User(
    name='John Doe',
    username='jdoe',
    date_joined=datetime.datetime.utcnow(),
    tags=['generic', 'anonymous']
)

如上(满足#2)和

User({
    'name': u'John Doe',
    'username': u'jdoe',
    'active': False,
    'date_joined': '2013-08-05T02:46:58Z',
    'tags': [u'generic', u'anonymous']
})

(满足#1)。这两种样式也可以混合使用

User({
    'name': u'John Doe',
    'username': u'jdoe',
    'active': False,
    'tags': [u'generic', u'anonymous']
}, date_joined=datetime.datetime.utcnow())

注意,在反序列化的JSON中,日期时间以字符串的形式存储,但在漂亮的Python对象中是datetime.datetime——我们将这些称为“json”表示和“python”表示,或者换句话说,“未展开”表示和“展开”表示。

注意。在调用构造函数时,请记住,关键字参数风格需要您传递“python”表示(例如一个datetime),而传递dict的json包装风格需要您以“json”表示(例如一个格式化的日期时间字符串)。

属性类型

主要有两种属性类型:标量类型(如字符串、布尔值、整数、日期时间等)和容器类型(列表、字典、集合)。以下分别介绍。

标量类型

除了它们各自类型特定的值(字符串、布尔值等)之外,所有标量属性还可以取None值。如果设置错误类型,属性会引发jsonobject.exceptions.BadValueError

class Foo(jsonobject.JsonObject):
    b = jsonobject.BooleanProperty()
>>> Foo(b=0)
Traceback (most recent call last):
  [...]
jsonobject.exceptions.BadValueError: 0 not of type <type 'bool'>

jsonobject.StringProperty

映射到unicode。用法

class Foo(jsonobject.JsonObject):
    s = jsonobject.StringProperty()

如果您将其设置为ASCII str,它将隐式转换为unicode

>>> Foo(s='hi')  # converts to unicode
Foo(s=u'hi')

如果您将其设置为非ASCII str,它将引发UnicodeDecodeError

>>> Foo(s='\xff')
Traceback (most recent call last):
  [...]
UnicodeDecodeError: 'ascii' codec can't decode byte 0xff in position 0: ordinal not in range(128)

jsonobject.BooleanProperty

映射到bool

jsonobject.IntegerProperty

映射到intlong

jsonobject.FloatProperty

映射到float

jsonobject.DecimalProperty

映射到decimal.Decimal并存储为JSON字符串。与FloatProperty不同,这种类型存储数字的“人类”表示。用法

class Foo(jsonobject.JsonObject):
    number = jsonobject.DecimalProperty()

如果您将其设置为intfloat,它将隐式转换为Decimal

>>> Foo(number=1)
Foo(number=Decimal('1'))
>>> Foo(number=1.2)
Foo(number=Decimal('1.2'))

但是,如果您将其设置为strunicode,则会引发AssertionError

>>> Foo(number='1.0')
Traceback (most recent call last):
  [...]
AssertionError

待办事项:这应该真正引发一个BadValueError

如果您传递的JSON中Decimal值是一个strunicode,但格式不正确,它将抛出与decimal.Decimal相同的错误。

>>> Foo({'number': '1.0'})
Foo(number=Decimal('1.0'))
>>> Foo({'number': '1.0.0'})
Traceback (most recent call last):
  [...]
decimal.InvalidOperation: Invalid literal for Decimal: '1.0.0'

jsonobject.DateProperty

映射到datetime.date并以格式为'%Y-%m-%d'的JSON字符串存储。用法

class Foo(jsonobject.JsonObject):
    date = jsonobject.DateProperty()

包装格式错误的字符串会引发BadValueError

>>> Foo({'date': 'foo'})
Traceback (most recent call last):
  [...]
jsonobject.exceptions.BadValueError: 'foo' is not a date-formatted string

jsonobject.DateTimeProperty

映射到无时区datetime.datetime并以格式为'%Y-%m-%dT%H:%M:%SZ'的JSON字符串存储。

尽管它对良好输入处理得很好,但在处理不符合指定格式的输入时却非常随意。它不会严格匹配,而是将字符串截断为前19个字符,并尝试将其解析为'%Y-%m-%dT%H:%M:%S'。这忽略了微秒,甚至更糟糕的是,忽略了时区。这是从couchdbkit继承下来的。

在jsonboject的新版本中,您可以选择指定一个DateTimePropertyexact

class Foo(jsonobject.JsonObject):
    date = jsonobject.DateTimeProperty(exact=True)

这提供了一个更干净的转换模型,具有以下属性

  1. 它保留了微秒
  2. 传入的JSON表示必须与'%Y-%m-%dT%H:%M:%S.%fZ'完全匹配。(这与默认输出类似,但必须有6位小数,即毫秒。)
  3. 与精确匹配不符的表示将被拒绝,并抛出BadValueError错误。

建议:如果您没有受到couchdbkit早期不良行为的限制,您应该始终在DateTimePropertyTimeProperty(以下)上使用exact=True标志。

jsonobject.TimeProperty

映射到datetime.time,以'%H:%M:%S'格式的JSON字符串形式存储。

要访问毫秒并使用严格行为,请使用exact=True设置,该设置严格接受格式'%H:%M:%S.%f'。这总是推荐的做法。有关更多信息,请参阅上一节关于DateTimeProperty的内容。

容器类型

容器类型通常需要一个参数,即item_type,用于指定包含对象的类型。

jsonobject.ObjectProperty(item_type)

映射到由item_type指定的模式的dictitem_type必须是其自身的JsonObject子类。用法

class Bar(jsonobject.JsonObject):
    name = jsonobject.StringProperty()


class Foo(jsonobject.JsonObject):
    bar = jsonobject.ObjectProperty(Bar)

如果未指定,它将被设置为具有默认值的新的对象。

>>> Foo()
Foo(bar=Bar(name=None))

如果您想将其设置为None,则必须显式地这样做。

jsonobject.ListProperty(item_type)

映射到类型为item_typelistitem_type可以是以下任何一种

  • 属性类的实例。这是最灵活的选项,所有验证(如required等)都将根据属性实例指定的方式进行。
  • 属性类,它将以required=True的方式实例化
  • 它们对应的Python类型之一(例如,int对应于IntegerProperty等)
  • JsonObject的子类

请注意,属性类(以及相关的Python类型语法)将以required=True的方式实例化,因此ListProperty(IntegerProperty)ListProperty(int)不允许None,而ListProperty(IntegerProperty())则允许None

给定任何项目类型的序列化行为将递归地应用于列表中的每个成员。

如果未指定,它将被设置为空列表。

jsonobject.SetProperty(item_type)

映射到set,并以列表的形式存储(仅包含唯一元素)。否则,它的行为与ListProperty非常相似。

jsonobject.DictProperty(item_type)

映射到具有字符串键和由item_type指定的值的dict。否则,它的行为与ListProperty非常相似。

如果未指定,它将被设置为空字典。

其他

jsonobject.DefaultProperty()

这灵活地包装了任何有效的JSON,包括所有标量和容器类型,动态检测值的类型并使用相应的属性对其进行处理。

属性选项

可以将某些参数传递给任何属性。

例如,下面的例子中的required就是这样一个参数

class User(JsonObject):
    username = StringProperty(required=True)

以下是属性列表的完整列表

  • default

    指定属性的默认值

  • name

    JSON表示中属性的名称。默认情况下,它将默认为Python属性的名称,但您可以在需要时覆盖它。这很有用,例如,可以绕过与Python关键字的冲突

    >>> class Route(JsonObject):
    ...     from_ = StringProperty(name='from')
    ...     to = StringProperty()  # name='to' by default
    >>> Route(from_='me', to='you').to_json()
    {'from': u'me', 'to': u'you'}
    

    请注意,在Python属性名称('from_')中存在下划线,但在JSON属性名称('from')中不存在。

    **如果您想知道为什么在上述例子中`StringProperty`的`name`参数可能默认为`to`,当它没有在初始化时访问`Route`类的属性时,您是完全正确的。描述的行为是在`JsonObject`的`__metaclass__`中实现的,它确实具有访问`Route`类属性的能力。
  • choices

    属性的允许值列表。(除非另有说明,否则也允许None

  • required

    默认为False。对于标量属性,requires意味着不能使用值None。对于容器属性,意味着它们不能为空或取值None

  • exclude_if_none

    默认为False。当设置为true时,当该属性的值为falsey时,此属性将不包括在JSON输出中。(请注意,目前这与其参数名称相矛盾,因为条件是它为falsey,而不是它是None)。

  • validators

    单个验证函数或验证函数列表。每个验证函数在无效输入时应抛出异常,否则不执行任何操作。

  • verbose_name

    此属性不执行任何操作,仅为了与couchdbkit的API兼容而添加。

与Couchdbkit的性能比较

为了与couchdbkit进行直接比较,测试套件包括一个大型样本模式,该模式最初是用couchdbkit编写的。很容易将jsonobject替换为couchdbkit,并分别运行测试。以下是结果:

$ python -m unittest test.test_couchdbkit
....
----------------------------------------------------------------------
Ran 4 tests in 1.403s

OK
$ python -m unittest test.test_couchdbkit
....
----------------------------------------------------------------------
Ran 4 tests in 0.153s

OK

开发生命周期

jsonobject版本遵循语义版本控制。版本信息可在CHANGES.md中找到。

开发者和维护者信息,例如如何运行测试和发布新版本,可在LIFECYCLE.md中找到。

项目详情


下载文件

下载适用于您平台的文件。如果您不确定选择哪个,请了解更多关于安装包的信息。

源代码分发

jsonobject-2.2.0.tar.gz (405.5 KB 查看哈希值

构建分发

jsonobject-2.2.0-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.whl (1.8 MB 查看哈希值)

上传时间 CPython 3.12 manylinux: glibc 2.5+ x86-64

支持者