我在 postgresql 数据库 (9.4.1) 的 JSON(不是 JSONB)列中存储了一些 JSON 数据。其中一些 JSON 结构在其属性值中包含 unicode 序列。例如:
{"client_id": 1, "device_name": "FooBar\ufffd\u0000\ufffd\u000f\ufffd" }
当我尝试查询此 JSON 列时(即使我没有直接尝试访问
device_name
属性),我收到以下错误:
错误:不支持的 Unicode 转义序列
详细:无法转换为文本。\u0000
您可以通过在 postgresql 服务器上执行以下命令来重新创建此错误:
select '{"client_id": 1, "device_name": "FooBar\ufffd\u0000\ufffd\u000f\ufffd" }'::json->>'client_id'
这个错误对我来说是有意义的 - 根本没有办法在文本结果中表示 unicode 序列
NULL
。
有什么方法可以让我查询相同的 JSON 数据,而不必对传入数据执行“清理”?这些 JSON 结构会定期更改,因此扫描特定属性(在本例中为
device_name
)并不是一个好的解决方案,因为很可能存在其他可能保存类似数据的属性。
经过更多调查,似乎此行为是版本 9.4.1 中的新行为,如更改日志中提到的:
...因此,当需要转换为反转义形式时,现在 json 值中的
也将被拒绝。只要不对值进行任何处理,此更改就不会破坏在 json 列中存储\u0000
的能力...\u0000
这真的是故意的吗?降级到 9.4.1 之前的版本是可行的选择吗?
附带说明一下,此属性取自客户端移动设备的名称 - 是用户将此文本输入到设备中。用户到底是如何插入
NULL
和 REPLACEMENT CHARACTER
值的?!
\u0000
是在字符串中无效的一个 Unicode 代码点。除了清理字符串之外,我没有其他方法。
由于
json
只是特定格式的字符串,因此您可以使用标准字符串函数,而不必担心 JSON 结构。删除代码点的单行清理程序是:
SELECT (regexp_replace(the_string::text, '\\u0000', '', 'g'))::json;
但是您也可以插入您喜欢的任何字符,如果将零代码点用作某种形式的分隔符,这将很有用。
还要注意数据库中存储的内容和向用户呈现的方式之间的细微差别。您可以将代码点存储在 JSON 字符串中,但在将值处理为
json
数据类型之前,必须将其预处理为其他字符。
我找到了适合我的解决方案
SELECT (regexp_replace(the_string::text, '(?<!\\)\\u0000', '', 'g'))::json;
注意匹配模式 '(?.
首先我可以通过写来重现错误:
select json '{ "a": "null \u0000 escape" }' ->> 'a' as fails
然后我添加了一个在查询中使用的自定义函数:
CREATE OR REPLACE FUNCTION null_if_invalid_string(json_input JSON, record_id UUID)
RETURNS JSON AS $$
DECLARE json_value JSON DEFAULT NULL;
BEGIN
BEGIN
json_value := json_input ->> 'location';
EXCEPTION WHEN OTHERS
THEN
RAISE NOTICE 'Invalid json value: "%". Returning NULL.', record_id;
RETURN NULL;
END;
RETURN json_input;
END;
$$ LANGUAGE plpgsql;
要调用该函数,请执行以下操作。您不应该收到错误。
select null_if_invalid_string('{ "a": "null \u0000 escape" }', id) from my_table
这应该按预期返回 json:
select null_if_invalid_string('{ "a": "null" }', id) from my_table
这不是确切问题的解决方案,但在某些类似情况下,如果您只是
不希望 json 中包含空字节的数据集,那么这是解决方案。只需添加:
AND json NOT LIKE '%\u0000%'
在 WHERE 语句中。您还可以使用 REPLACE SQL 语法来清理数据:
REPLACE(source_field, '\u0000', '' );
update ___MY_TABLE___
set settings = REPLACE(settings::text, '\u0000', '' )::json
where settings::text like '%\u0000%'
psycopg2
的 Python 代码中出错。我无法逃脱
\n0000
中的反斜杠。解决方案是使用足够的转义字符来满足 Postgres 以及 Python 解释器的要求。我总共需要使用四 (4) 个反斜杠来转义
\n0000
:
\\\\n0000
如果这在 Python 中的 SQL 查询字符串中仍然不起作用,您可能必须对其进行参数化,例如:
query = sql.SQL("""
select (regexp_replace(data::text, %s, '', 'g'))::json...
""")
...
....
cursor.execute(query, ('\\\\u0000',))