SQLite json_object 的值参数:内容像数组,为什么最终变成了字符串

10-01 3阅读

参数绑定保住了文本,却未决定结构

准备接口响应时,把已有数组文本传给 json_object,结果可能是 items 字段里套着一段带方括号的字符串。原始字符都在,JSON 也合法,但下游需要的是数组,类型已经不对。这里需要检查值参数在当前表达式中被如何解释,不能仅凭文本看起来像 JSON 就认定会自动展开。

下面使用 Python 3.12.14 与 SQLite 3.53.1,只运行内存连接上的查询。实验不建业务表、不修改外部数据库,输入就是两个整数的数组文本。保存为 demo.py 后运行 python demo.py;如果运行环境没有编入 JSON 功能,应先确认自己的 SQLite 构建,而不是修改输入碰运气。

对照三条数据路径

第一条把 Python 字符串直接绑定给 json_object 的值参数,得到 JSON 字符串值。第二条在同一条 SQL 里用 json() 处理该参数,其直接结果作为 JSON 值嵌入,得到真正的数组。两条路径都使用参数绑定,因此差别不是有没有正确转义 SQL,而是构造函数收到的值语义不同。

第三条先单独调用 json(),把返回值取回 Python,再作为参数绑定到新查询。此时传回数据库的是普通 Python 字符串,不会因为它曾经经过 json() 就永久携带嵌套结构身份。代码用反序列化后的字段类型和内容断言这一点,再检查 SQL 空值与文本 null 的构造差别。

SQLite json_object 的值参数:内容像数组,为什么最终变成了字符串

AI概念示意图:相同元素被整体封成文本卡,或分别放入可取出元素的数组托盘。图片只说明概念,不是运行截图。

import json
import sqlite3

con = sqlite3.connect(":memory:")
raw = "[4,5]"


def scalar(sql, args=()):
    return con.execute(sql, args).fetchone()[0]


try:
    text_value = scalar("SELECT json_object('items', ?)", (raw,))
    array_value = scalar("SELECT json_object('items', json(?))", (raw,))
    assert json.loads(text_value)["items"] == "[4,5]"
    assert json.loads(array_value)["items"] == [4, 5]
    assert scalar("SELECT json_type(?, '$.items')", (text_value,)) == "text"
    assert scalar("SELECT json_type(?, '$.items')", (array_value,)) == "array"
    print("plain:", text_value)
    print("nested:", array_value)

    returned = scalar("SELECT json(?)", (raw,))
    assert type(returned) is str
    rebound = scalar("SELECT json_object('items', ?)", (returned,))
    assert rebound == text_value
    print("rebound:", rebound)

    nulls = scalar("SELECT json_object('empty', ?, 'word', ?)", (None, "null"))
    assert json.loads(nulls) == {"empty": None, "word": "null"}
    kinds = con.execute("SELECT json_type(?, '$.empty'), json_type(?, '$.word')",
                        (nulls, nulls)).fetchone()
    assert kinds == ("null", "text")
    print("nulls:", nulls)
    print("types:", kinds)
finally:
    con.close()

检查字段类型,不只比较字符串外观

前两行分别是 plain: {"items":"[4,5]"} 与 nested: {"items":[4,5]}。双引号包住整个方括号片段时,items 是一个字符串;没有这层引号时才是数组。两个文档都能被 JSON 解析器读取,所以只验证“没有语法错误”不足以证明响应符合接口要求。

第三行 rebound 与 plain 相同,验证取回再绑定的路径失去了本次表达式里的 JSON 值语义。若下一条构造查询仍需要嵌入数组,就应在那个位置再次明确使用 json(),并处理输入不是合格 JSON 时的错误。不要把某次函数处理成功误记成字符串今后在所有调用中都有同样身份。

最后两行展示 {"empty":null,"word":"null"} 以及类型元组 (null, text),实际元组输出带引号。绑定的 Python None 对应 SQL NULL,构造后成为 JSON 空值;普通文本 null 则仍是四个字符的字符串。类型检查能直接区分二者,不需要自行删除引号或猜测特殊词。

在每个构造边界写清输入契约

不是每个字符串都应该包进 json()。如果字段本来要保存用户输入的原文,强行解析反而改变含义或产生错误。先约定字段是普通文字、数组还是对象,再选择构造路径,并在输出端验证关键字段类型。参数绑定负责把值交给 SQL,业务结构仍需要应用自己说明。

本例只讨论文本 JSON 构造与 Python 往返,没有覆盖 JSONB、复杂路径或存储优化。测试真实接口时,可以保留普通数组文本、再次绑定的文本、SQL 空值与字面字符串这几组样本。既比较解析后的结构,也检查数据库返回的字段类型,能更早发现一层多余编码造成的接口变化。

参考资料

文章版权声明:除非注明,否则均为云鹊BLOG原创文章,转载或复制请以超链接形式并注明出处。