JSON 数据类型支持

概述

最近,所有主要的数据库系统都添加了对原生 JSON 数据类型的支持。JSON 支持引入了通常在 NoSQL 数据库中发现的动态数据结构。通常,它们在处理高度可变的数据或难以预测确切数据结构时使用。

Pony 允许使用 Python 语法处理存储在数据库中的 JSON 数据。

声明 JSON 属性

要使用 Pony 声明 JSON 属性,您应该使用 Json 类型。此类型可以从 pony.orm 包中导入

from pony.orm import *

db = Database()


class Product(db.Entity):
    id = PrimaryKey(int, auto=True)
    name = Required(str)
    info = Required(Json)
    tags = Optional(Json)


db.bind('sqlite', ':memory:', create_db=True)
db.generate_mapping(create_tables=True)

Product 实体中的 info 属性被声明为 Json。这使我们能够为不同的产品类型拥有不同的 JSON 结构,并对这些数据进行查询。

为 JSON 属性赋值

通常,JSON 结构包含字典和列表的组合,这些字典和列表包含简单类型,例如数字、字符串和布尔值。让我们创建几个具有简单 JSON 结构的对象

p1 = Product(name='Samsung Galaxy S7 edge',
             info={
                 'display': {
                    'size': 5.5,
                 },
                 'battery': 3600,
                 '3.5mm jack': True,
                 'SD card slot': True,
                 'colors': ['Black', 'Grey', 'Gold'],
             },
             tags=['Smartphone', 'Samsung'])

p2 = Product(name='iPhone 6s',
             info={
                 'display': {
                    'size': 4.7,
                    'resolution': [750, 1334],
                    'multi-touch': True,
                 },
                 'battery': 1810,
                 '3.5mm jack': True,
                 'colors': ['Silver', 'Gold', 'Space Gray', 'Rose Gold'],
             },
             tags=['Smartphone', 'Apple', 'Retina'])

在 Python 代码中,JSON 结构借助标准 Python dict 和 list 表示。在我们的示例中,info 属性被分配了一个 dict。tags 属性保留了一个标签列表。这些属性将被序列化为 JSON,并在提交时存储在数据库中。

读取 JSON 属性

您可以像读取任何其他实体属性一样读取 JSON 属性

>>> Product[1].info
{'battery': 3600, '3.5mm jack': True, 'colors': ['Black', 'Grey', 'Gold'],
'display': 5.5}

从数据库中提取 JSON 属性后,它将被反序列化并表示为 dict 和 list 的组合。您可以使用标准 Python dict 和 list API 处理该值

>>> Product[1].info['colors']
['Black', 'Grey', 'Gold']

>>> Product[1].info['colors'][0]
'Black'

>>> 'Black' in Product[1].info['colors']
True

修改 JSON 属性

要修改 JSON 属性值,您也可以使用标准 Python list 和 dict API

>>> Product[1].info['colors'].append('Silver')
>>> Product[1].info['colors']
['Black', 'Grey', 'Gold', 'Silver']

现在,在提交时,更改将存储在数据库中。为了跟踪 JSON 结构中所做的更改,Pony 使用它自己的 dict 和 list 实现,这些实现继承自标准 Python dict 和 list。

以下是一些关于如何修改 JSON 值的示例。

p = Product[1]

# assigning a new value
p.info['display']['size'] = 4.7

# popping a dict value
display_size = p.info['display'].pop('size')

# removing a dict key using del
del p.info['display']

# adding a dict key
p.info['display']['resolution'] = [1440, 2560]

# removing a list item
del p.info['colors'][0]

# replacing a list item
p.info['colors'][1] = ['White']

# replacing a number of list items
p.info['colors'][1:] = ['White']

所有上述操作都是对属性、列表和字典的常规 Python 操作。

查询 JSON 结构

数据库中的原生 JSON 支持不仅允许读取和修改结构化数据,还允许进行查询。这是一个非常强大的功能 - 查询使用相同的语法并在相同的 ACID 事务环境中运行,同时在关系数据库内部提供文档存储的 NoSQL 功能。

Pony 允许通过按 JSON 子元素过滤来选择对象。为了访问 JSON 子元素,Pony 构造 JSON 路径表达式,然后将其用于 SQL 查询中

# products with display size greater than 5
Product.select(lambda p: p.info['display']['size'] > 5)

为了指定值,您可以使用参数

x = 2048
# products with width resolution greater or equal to x
Product.select(lambda p: p.info['display']['resolution'][0] >= x)

在 MySQL、PostgreSQL、CockroachDB 和 SQLite 中,也可以在 JSON 路径表达式中使用参数

index = 0
Product.select(lambda p: p.info['display']['resolution'][index] < 2000)

key = 'display'
Product.select(lambda p: p.info[key]['resolution'][index] > 1000)

注意

Oracle 不支持 JSON 路径中的参数。使用 Oracle 时,您只能使用常量键。

对于 JSON 数组,您可以计算长度

# products with more than 2 tags
Product.select(lambda p: len(p.info['tags']) > 2)

另一个查询示例是检查字符串键是否是 JSON dict 或数组的一部分

# products which have the resolution specified
Product.select(lambda p: 'resolution' in p.info['display'])

# products of black color
Product.select(lambda p: 'Black' in p.info['colors'])

当您将 JSON 子元素与 None 进行比较时,它将在以下情况下被评估为 True

  • 当子元素包含 JSON null 值时

  • 当子元素不存在时

Product.select(lambda p: p.info['SD card slot'] is None)

您可以测试 JSON 子元素的真值

# products with multi-touch displays
select(p for p in Product if p.info['display']['multi-touch'])

在 Python 中,以下值在条件语句中被视为假:None、0、False、空字符串、空字典和空列表。Pony 在应用于 JSON 结构的条件中保留此行为。此外,如果找不到 JSON 路径,它将被评估为假。

在前面的示例中,我们在查询条件中使用了 JSON 结构。但也可以检索 JSON 结构或将其部分作为查询结果提取

select(p.info['display'] for p in Product)

以这种方式检索 JSON 结构时,它们不会链接到实体实例。这意味着对这些 JSON 结构的修改不会保存到数据库中。Pony 仅在您选择对象并修改其属性时跟踪 JSON 更改。

MySQL 和 Oracle 允许在 JSON 路径中使用通配符。Pony 通过使用特殊语法支持通配符

  • […] 表示“任何字典元素”

  • [:] 表示“任何列表项”

以下是一个查询示例

select(p.info['display'][...] for p in Product)

此类查询的结果将是一个 JSON 子元素数组。在数据库中当前的 JSON 支持情况下,通配符只能在生成器表达式的表达式部分使用。

数据库中的 JSON 支持

为了将 JSON 存储在数据库中,Pony 使用以下类型

从 3.9 版本开始,SQLite 提供了 JSON1 扩展模块。此扩展在处理 JSON 查询时提高了性能,尽管 Pony 即使没有此模块也可以在 SQLite 中处理 JSON。