返回博客

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]",然后尝试解析这串字符——当然炸了。

坑:mysql2 v3 会自动解析 JSON 列,再手动 JSON.parse 必炸
  • mysql2 v2:JSON 列返回字符串,需要手动 JSON.parse
  • mysql2 v3: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 类型,混合了两种列,按字段级守卫更安全,不会因为全局配置漏改某个地方而留下隐患。

正解:typeof 守卫,字符串才 parse,对象直接透传
const parsed = typeof raw === 'string' ? JSON.parse(raw) : raw;

一行代码同时兼容 mysql2 v2 / v3,以及 TEXT / JSON 列混用场景。

一句话外卖

升级 mysql2 v3 后,不要对 JSON 类型列重复调用 JSON.parse——驱动已经帮你做了,加 typeof === 'string' 守卫才是版本无关的写法。