mysql2 v3 升级后 JSON 列被二次解析,500 报错 Unexpected token o
某后台管理系统升级 mysql2 从 v2 到 v3 之后,线上部分接口开始 500。报错信息非常迷惑:
SyntaxError: Unexpected token o in JSON at position 1
本地跑居然是好的——因为本地还装着旧版本。一旦 CI 拉取最新依赖,问题必现。而且只有那些 JSON 列有数据的行才炸,NULL 行一路平安。
现象
- 接口返回 500,堆栈指向某个
JSON.parse(row.xxx)调用 - 错误信息是
Unexpected token o in JSON at position 1(即字符串"[object Object]"的首字符o) - 数据库里该列的 DDL 类型是
JSON,不是TEXT - 从 mysql2 v2 升级到 v3 后才出现,回退版本后消失
根因
mysql2 v3 开始,驱动层会自动把 JSON 类型列反序列化为 JavaScript 对象。也就是说,row.options 拿到的已经是 { … } 对象,不再是原始字符串。
旧代码是为 v2(或 TEXT 列存 JSON 字符串)写的,习惯性地调用:
options: col.options ? JSON.parse(col.options) : [],v3 环境下,col.options 已经是对象,JSON.parse(someObject) 会先执行 String(someObject) 得到 "[object Object]",然后尝试解析这串字符——当然炸了。
mysql2v2:JSON列返回字符串,需要手动JSON.parsemysql2v3:JSON列返回已解析对象,手动JSON.parse再解析一次 →"[object Object]"→SyntaxError
只有列 DDL 是 JSON 类型时才触发,TEXT/LONGTEXT 存的 JSON 字符串不受影响——这也是为什么部分行(NULL 行)不炸:null 被短路,没走到 JSON.parse。
正解
用 typeof 守卫做防御性处理,让代码同时兼容"还没迁移的 TEXT 列"和"已升级的 JSON 列":
// 修复后——防御性写法,兼容 v2/v3 及 TEXT/JSON 列
options: col.options
? (typeof col.options === 'string' ? JSON.parse(col.options) : col.options)
: [],
optionColors: col.option_colors
? (typeof col.option_colors === 'string' ? JSON.parse(col.option_colors) : col.option_colors)
: {},逻辑很简单:已经是对象就直接用,还是字符串才解析。
如果想在全局层面统一处理,可以在创建连接池时用 typeCast 强制让驱动返回原始字符串,然后由业务层统一解析:
const pool = mysql.createPool({
// ...
typeCast: (field, next) =>
field.type === 'JSON' ? field.string() : next(),
});不过这个做法相当于把 v3 的自动解析特性关掉了。如果项目正在从 TEXT 迁移到 JSON 类型,混合了两种列,按字段级守卫更安全,不会因为全局配置漏改某个地方而留下隐患。
const parsed = typeof raw === 'string' ? JSON.parse(raw) : raw;一行代码同时兼容 mysql2 v2 / v3,以及 TEXT / JSON 列混用场景。
一句话外卖
升级 mysql2 v3 后,不要对
JSON类型列重复调用JSON.parse——驱动已经帮你做了,加typeof === 'string'守卫才是版本无关的写法。