跳到内容

JSON 支持

Lance 提供对存储和查询 JSON 数据的全面支持,使您能够高效地处理半结构化数据。本指南介绍如何在 Lance 数据集中存储 JSON 数据,以及使用 JSON 函数查询和过滤数据。

先决条件

JSON 支持需要 Lance 数据存储版本 2.2 或更高版本

import lance
import pyarrow as pa
import json

# Create a table with JSON data
json_data = {"name": "Alice", "age": 30, "city": "New York"}
json_arr = pa.array([json.dumps(json_data)], type=pa.json_())
table = pa.table({"id": [1], "data": json_arr})

# Write with version 2.2 (required for JSON support)
lance.write_dataset(table, "dataset.lance", data_storage_version="2.2")

存储格式

Lance 内部使用 lance.json 扩展类型将 JSON 数据存储为 JSONB(二进制 JSON)。这提供了: - 通过二进制编码实现高效存储 - 嵌套字段访问的快速查询性能 - 与 Apache Arrow 的 JSON 类型兼容

当您从 Lance 读取 JSON 数据时,它会自动转换为 Arrow 的 JSON 类型,以便与您的数据处理管道无缝集成。

JSON 函数

Lance 提供了一套全面的 JSON 函数,用于查询和过滤 JSON 数据。这些函数可以在 to_table()scanner() 等方法中与过滤表达式一起使用,并通过 DataFusion 集成进行 SQL 查询。

数据访问函数

json_extract

使用 JSONPath 语法从 JSON 中提取值。

语法: json_extract(json_column, json_path)

返回: JSON 值作为字符串(包括字符串的引号)

示例

# Sample data: {"user": {"name": "Alice", "age": 30}}
result = dataset.to_table(
    filter="json_extract(data, '$.user.name') = '\"Alice\"'"
)

注意

json_extract 返回的字符串值包含 JSON 引号。与 '"Alice"' 而不是 'Alice' 进行比较。

json_get

从 JSON 中检索字段或数组元素,并将其作为 JSON 返回。

语法: json_get(json_column, key)

返回: JSON 值(可用于嵌套访问)

示例

# Access nested JSON by chaining json_get calls
# Sample data: {"user": {"profile": {"name": "Alice"}}}
result = dataset.to_table(
    filter="json_get_string(json_get(json_get(data, 'user'), 'profile'), 'name') = 'Alice'"
)

类型安全的数值提取

这些函数提取值时包含类型转换和验证

json_get_string

从 JSON 中提取字符串值。

语法: json_get_string(json_column, key)

返回: 字符串值(不带 JSON 引号)

示例

result = dataset.to_table(
    filter="json_get_string(data, 'name') = 'Alice'"
)

json_get_int

使用类型强制转换提取整数值。

语法: json_get_int(json_column, key)

返回: 64 位整数(如果可能,转换字符串)

示例

# Works with both numeric and string values
# {"age": 30} or {"age": "30"} both work
result = dataset.to_table(
    filter="json_get_int(data, 'age') > 25"
)

json_get_float

使用类型强制转换提取浮点值。

语法: json_get_float(json_column, key)

返回: 64 位浮点数(如果可能,转换字符串)

示例

result = dataset.to_table(
    filter="json_get_float(data, 'score') >= 90.5"
)

json_get_bool

使用类型强制转换提取布尔值。

语法: json_get_bool(json_column, key)

返回: 布尔值(转换“true”/“false”等字符串、数字)

示例

result = dataset.to_table(
    filter="json_get_bool(data, 'active') = true"
)

存在性和数组函数

json_exists

检查 JSONPath 是否存在于 JSON 数据中。

语法: json_exists(json_column, json_path)

返回: 布尔值

示例

# Find records that have an age field
result = dataset.to_table(
    filter="json_exists(data, '$.user.age')"
)

json_array_contains

检查 JSON 数组是否包含特定值。

语法: json_array_contains(json_column, json_path, value)

返回: 布尔值

示例

# Sample data: {"tags": ["python", "ml", "data"]}
result = dataset.to_table(
    filter="json_array_contains(data, '$.tags', 'python')"
)

json_array_length

返回 JSON 数组的长度。

语法: json_array_length(json_column, json_path)

返回: 整数(对于非数组或缺失路径返回 0)

示例

# Find records with more than 3 tags
result = dataset.to_table(
    filter="json_array_length(data, '$.tags') > 3"
)

使用示例

处理嵌套 JSON

import lance
import pyarrow as pa
import json

# Create nested JSON data
data = [
    {
        "id": 1,
        "user": {
            "profile": {
                "name": "Alice",
                "settings": {
                    "theme": "dark",
                    "notifications": True
                }
            },
            "scores": [95, 87, 92]
        }
    },
    {
        "id": 2,
        "user": {
            "profile": {
                "name": "Bob",
                "settings": {
                    "theme": "light",
                    "notifications": False
                }
            },
            "scores": [88, 91, 85]
        }
    }
]

# Convert to Lance dataset
json_strings = [json.dumps(d) for d in data]
table = pa.table({
    "data": pa.array(json_strings, type=pa.json_())
})

lance.write_dataset(table, "nested.lance", data_storage_version="2.2")
dataset = lance.dataset("nested.lance")

# Query nested fields using JSONPath
dark_theme_users = dataset.to_table(
    filter="json_extract(data, '$.user.profile.settings.theme') = '\"dark\"'"
)

# Or using chained json_get
high_scorers = dataset.to_table(
    filter="json_array_length(data, '$.user.scores') >= 3"
)

JSON 与其他数据类型结合

# Create mixed-type table with JSON metadata
products = pa.table({
    "id": [1, 2, 3],
    "name": ["Laptop", "Phone", "Tablet"],
    "price": [999.99, 599.99, 399.99],
    "specs": pa.array([
        json.dumps({"cpu": "i7", "ram": 16, "storage": 512}),
        json.dumps({"screen": 6.1, "battery": 4000, "5g": True}),
        json.dumps({"screen": 10.5, "battery": 7000, "stylus": True})
    ], type=pa.json_())
})

lance.write_dataset(products, "products.lance", data_storage_version="2.2")
dataset = lance.dataset("products.lance")

# Find products with specific specs
result = dataset.to_table(
    filter="price < 600 AND json_get_bool(specs, '5g') = true"
)

处理 JSON 中的数组

# Create data with JSON arrays
records = pa.table({
    "id": [1, 2, 3],
    "data": pa.array([
        json.dumps({"name": "Project A", "tags": ["python", "ml", "production"]}),
        json.dumps({"name": "Project B", "tags": ["rust", "systems"]}),
        json.dumps({"name": "Project C", "tags": ["python", "web", "api", "production"]})
    ], type=pa.json_())
})

lance.write_dataset(records, "projects.lance", data_storage_version="2.2")
dataset = lance.dataset("projects.lance")

# Find projects with Python
python_projects = dataset.to_table(
    filter="json_array_contains(data, '$.tags', 'python')"
)

# Find projects with more than 3 tags
complex_projects = dataset.to_table(
    filter="json_array_length(data, '$.tags') > 3"
)

性能考量

  1. 使用特定的提取函数:像 json_get_string 这样的函数比 json_extract 对于简单的字段访问更高效。
  2. 索引频繁查询的路径:考虑为频繁访问的 JSON 路径创建计算列,以提高查询性能。
  3. 尽量减少深度嵌套:虽然 Lance 支持任意嵌套,但更扁平的结构通常性能更好。
  4. 类型安全函数:当您知道预期类型时,使用特定类型的函数(json_get_intjson_get_bool),因为它们可以有效地处理类型强制转换。

与 DataFusion 集成

当 Lance 与 Apache DataFusion 一起用于 SQL 查询时,所有 JSON 函数都可用。有关在 SQL 上下文中使用 JSON 函数的更多详细信息,请参阅DataFusion 集成指南。

限制

  • JSON 支持需要数据存储版本 2.2 或更高版本
  • JSONPath 支持遵循标准 JSONPath 语法,但可能不支持所有高级功能
  • 大型 JSON 文档可能会影响查询性能
  • JSON 函数目前仅适用于过滤,不适用于查询结果中的投影