JSON解析失败主因是聚合前未转义原始字段中的换行符、双引号、反斜杠;必须在JSON_OBJECT/JSON_ARRAYAGG调用前用REPLACE逐层清洗,且反斜杠须最先处理,否则导致转义错误。

JSON解析失败时,先确认是不是聚合字段里混了换行符或双引号
MySQL 的 JSON_OBJECT() 或 JSON_ARRAYAGG() 在遇到未转义的
、"、 时会直接报错 Invalid JSON text,而不是静默忽略。尤其当源字段来自用户输入、日志文本或富文本内容时,这类字符极常见——但很多人误以为是 JSON 函数本身有问题,其实根源在原始数据。
实操建议:
- 用
SELECT HEX(col), col FROM table WHERE col LIKE '% %' OR col LIKE '%"%' LIMIT 5;快速定位含特殊字符的值 - 别依赖应用层再清洗:聚合前不处理,
JSON_OBJECT('name', name)遇到带换行的name就崩,没机会 catch - 注意:MySQL 8.0+ 的
JSON_VALID()对这种场景无效,它只校验语法结构,不检查嵌入字符串是否已转义
用REPLACE逐层清洗比嵌套JSON_SET更可靠
有人想用 JSON_SET(JSON_OBJECT(...), '$.name', REPLACE(name, '
', '\n')),但这是错的——JSON_OBJECT() 在构造阶段就已失败,根本执行不到 JSON_SET。必须在传入 JSON 函数前,把原始字段里的危险字符替换成 JSON 兼容形式。
实操建议:
- 按优先级顺序替换:
REPLACE(REPLACE(REPLACE(col, '\', '\\'), '"', '\"'), ' ', '\n'),反斜杠必须最先处理,否则后续替换会把它变成\\导致冗余 - 如果字段还含
或,也得加进链式REPLACE,顺序同上(先处理) - 避免用正则
REGEXP_REPLACE():MySQL 8.0 虽支持,但性能差、可读性低,且部分版本对多字节转义支持不稳
GROUP_CONCAT + JSON_ARRAYAGG 混用时,清洗位置很关键
当需要先拼接再转 JSON 数组(比如把多行标签合并成一个数组字段),容易在 GROUP_CONCAT() 内部漏掉清洗,导致最终 JSON_ARRAYAGG() 输入的是已污染的字符串。
实操建议:
- 清洗必须发生在最内层:写成
JSON_ARRAYAGG(JSON_OBJECT('tag', REPLACE(t.tag_name, '"', '\"'))),而不是JSON_ARRAYAGG(JSON_OBJECT('tag', t.tag_name))再在外面套 REPLACE - 如果用了
GROUP_CONCAT(DISTINCT ... SEPARATOR ',')做去重拼接,记得清洗是在GROUP_CONCAT的表达式里做,例如GROUP_CONCAT(REPLACE(t.val, ' ', '\n') SEPARATOR '|') - 注意
GROUP_CONCAT默认长度限制 1024,超长会被截断,而截断点可能正好在转义符中间(如只剩\),导致 JSON 解析器看到非法序列
生产环境要防NULL和空字符串干扰JSON结构
REPLACE(NULL, 'x', 'y') 返回 NULL,而 JSON_OBJECT('k', NULL) 生成的是 {"k": null},这本身合法;但若业务要求字段必为字符串,NULL 就会引发下游解析异常。更麻烦的是空字符串 '' —— 它不会报错,但可能被前端当成缺失字段忽略。
实操建议:
- 统一兜底:用
COALESCE(REPLACE(col, '"', '\"'), '')确保非 NULL,再根据业务决定是否进一步转成'(empty)'或'N/A' - 警惕
TRIM()后残留的不可见字符:某些编辑器粘贴进来的字段看着空,实际含u200b(零宽空格),MySQL 不识别这个 Unicode,REPLACE也无能为力,得用REGEXP_REPLACE(col, '[^[:print:]]', '')辅助清理 - 所有清洗逻辑上线前,务必用真实脏数据集跑一次
SELECT ... INTO OUTFILE,再用 Python 的json.loads()验证输出文件是否可解析
真正难的不是写几层 REPLACE,而是得想清楚清洗发生在哪一层、针对谁生效、以及下游系统到底接受什么格式的 JSON。少一层 REPLACE 可能只是前端显示错位,多一层错误的转义却会让整个 API 响应 500。


















