serde-sqlite-jsonb
此 crate 为 SQLite JSONB 列提供了一个自定义的 Serde 反序列化器。
它最初是为纳入 SQLPage 网站构建器而开发的。
为什么
自 3.45.0 版本起,SQLite 支持 JSONB 列, 它可以以二进制格式存储 JSON 数据,该格式比 JSON 更高效 便于操作。
问题在于,使用 SQLite 的应用程序目前需要 将数据从 JSONB 转换为 JSON,然后再从 JSON 转换为其 自身的数据结构才能使用。 这阻止了使用 SQLite 的 blob 流式 API 以流式方式直接从 数据库读取 blob 数据, 并且需要执行 SQL 查询来提取并将数据转换为 JSON。
此 crate 为 JSONB 直接提供了自定义的 Serde 序列化器和反序列化器, 从而允许跳过 JSON 转换步骤。
在某些场景下,这可以带来显著的性能提升, 如本 crate 的基准测试所示。
基准测试
这些图表显示了所花费的时间:
- 使用此 crate 将 JSONB 列直接反序列化为结构体
- 执行 SQL 查询将 JSONB 列提取为 JSON,然后使用 serde_json 将其反序列化为结构体。
被反序列化的数据包含一个字符串,其长度从 50 到 1000 个字符不等,以展示性能随数据大小变化的趋势。
免责声明:这些基准测试应始终持保留态度看待。 当性能至关重要时,您应在自己的应用程序中使用自己的数据来测量性能。
serde_json经过非常良好的优化,在某些场景下可能比本 crate 更快, 尤其是当 JSON 数据较小时。
Crate 特性
二进制格式可以包含原始 json 数据,因此本 crate 依赖于 serde_json crate 来解析 JSON 数据。
由于 SQLite 也支持 json5,如果需要 json5 支持,可以使用 serde-json5 特性。
默认情况下,(更快的)serde_json 特性已启用,当尝试解析 json5 数据时,本 crate 会返回错误。
要启用 json5 支持,请启用 serde-json5 特性
(并可选择禁用默认特性,以便对 json 数据也使用 json5 解析器):
[dependencies]
serde-sqlite-jsonb = { version = "0.1", features = ["serde-json5"], default-features = false }
用法
本库不处理 SQLite 连接,
因此你需要使用 rusqlite 或 sqlx 等 crate 来与数据库交互。
一旦你从数据库中提取了 JSONB 数据,
无论是作为 Vec<u8> 还是作为从数据库流式传输 BLOB 数据的 std::io::Read 对象,
你都可以使用 serde_sqlite_jsonb crate 来反序列化 JSON 数据,
将其转换为你自己的数据结构或一个 serde_json::Value。
从查询结果中反序列化 JSONB
let conn = rusqlite::Connection::open_in_memory()?;
let blob: Vec<u8> = conn.query_row(
r#"select jsonb('{"id": 1, "name": "John Doe"}')"#, [], |row| row.get(0),
)?;
let person: Person = serde_sqlite_jsonb::from_bytes(&blob).unwrap();
从 SQLite BLOB 流式反序列化
let my_blob = conn.blob_open( // returns an object that implements std::io::Read
DatabaseName::Main,
"my_table", // table name
"my_jsonb_column", // column name
42, // primary key (rowid)
true // read-only
)?;
let parsed: serde_json::Value = // or any other type that implements Deserialize
serde_sqlite_jsonb::from_reader(my_blob).unwrap();
格式
JSONB 列的格式在 SQLite 文档中有描述: https://sqlite.org/draft/jsonb.html
数据格式是一种包含头部和负载的二进制格式。头部包含有关元素类型和负载大小的信息。负载包含实际数据。
以下是大致的 ASCII 表示:
bits: 0 1 2 3 4 5 6 7 8
+-------------+-------------+
| size(4) | type(4) | first header byte
+-------------+-------------+
| payload size (0 - 64) | header bytes number 2 to 9
+---------------------------+
| payload data | payload bytes (JSON strings or numbers in text format)
+---------------------------+
头部
负载大小
如果负载数据在 0 到 11 字节(含)之间,大小编码在头部的第一个 4 位中。 否则,负载的大小编码在后续字节中,且第一个 4 位指示用于编码负载大小的字节数,使用下表:
| 负载数据大小范围 | 大小编码 | 头部的第一个 4 位 |
|---|---|---|
| 0 到 11 字节 | u4 (嵌入在第一个 4 位中) | 0 到 11 (0x0 到 0xB) |
| 12 到 2^8 - 1 字节 | u8 | 12 (0xC) |
| 2^8 到 2^16 - 1 字节 | u16 | 13 (0xD) |
| 2^16 到 2^32 - 1 字节 | u32 | 14 (0xE) |
| 2^32 到 2^64 - 1 字节 | u64 | 15 (0xF) |
类型
| Type | Hex Code | Description |
|---|---|---|
Null | 0x0 | 该元素是 JSON "null"。 |
True | 0x1 | 该元素是 JSON "true"。 |
False | 0x2 | 该元素是 JSON "false"。 |
Int | 0x3 | 该元素是符合 RFC 8259 标准格式的 JSON 整数值。 |
Int5 | 0x4 | 该元素是 JSON5 整数,例如 0xABC。 |
Float | 0x5 | 该元素是符合 RFC 8259 标准格式的 JSON 浮点值。 |
Float5 | 0x6 | 该元素是不符合标准 JSON 格式的 JSON5 浮点值。 |
Text | 0x7 | 该元素是不包含任何转义字符的 JSON 字符串值。 |
TextJ | 0x8 | 该元素是包含 RFC 8259 字符转义的 JSON 字符串值。 |
Text5 | 0x9 | 该元素是包含字符转义的 JSON5 字符串值,其中包括一些来自 JSON5 的转义。 |
TextRaw | 0xA | 该元素是包含在 JSON 中需要转义的 UTF8 字符的 JSON 字符串值。 |
Array | 0xB | 该元素是 JSON 数组。第一个数组元素的头部紧跟在数组头部之后。 |
Object | 0xC | 该元素是 JSON 对象。对象键(字符串)和值在负载中交替出现。 |
Reserved13 | 0xD | 保留用于未来扩展。 |
Reserved14 | 0xE | 保留用于未来扩展。 |
Reserved15 | 0xF | 保留用于未来扩展。 |
示例
以下 JSON 对象:
{"a": false, "b":true}
被编码为以下 7 字节的二进制数据:
6c 17 61 02 17 62 01
| byte | value | description |
|---|---|---|
| 0 | 0x6c | 头部:负载大小 = 6,类型 = Object (0xC) |
| 1 | 0x17 | 头部:负载大小 = 1,类型 = Text (0x7) |
| 2 | 0x61 | 负载:'a' |
| 3 | 0x02 | 头部:负载大小 = 0,类型 = False (0x2) |
| 4 | 0x17 | 头部:负载大小 = 1,类型 = Text (0x7) |
| 5 | 0x62 | 负载:'b' |
| 6 | 0x01 | 头部:负载大小 = 0,类型 = True (0x1) |
MSRV
需要 rust >= 1.63 (debian stable)