MySQL 8.0+ 中 Laravel 可直接用 $table->json('meta') 定义 JSON 字段,配合 casts => ['meta' => 'array'] 实现自动序列化,但需确保字段类型正确、内容合法,否则 whereJsonContains 等方法失效。

MySQL 8.0+ 的 JSON 字段在 Laravel 中能直接用 json 类型迁移定义
只要数据库是 MySQL 8.0+(或 MariaDB 10.2+),Laravel 原生支持 json 字段类型,不需要手动写 TEXT + 自行序列化。但很多人误以为必须用 TEXT,结果后续查不到字段、whereJsonContains 失效。
- 迁移里直接写
$table->json('meta'),Laravel 会生成 MySQL 的JSON类型字段 - 低版本 MySQL(如 5.7)虽也支持
JSON类型,但部分 JSON 函数(如JSON_TABLE)不可用;Laravel 的whereJsonContains等方法底层依赖这些函数,所以务必确认 MySQL 版本 - 如果用了
TEXT存 JSON 字符串,Laravel 无法自动识别为 JSON 字段,casts设成'meta' => 'array'只能解决读取反序列化,写入仍是字符串,且所有 JSON 查询方法全部失效
Laravel 模型里用 casts 声明 JSON 字段时,array 和 object 行为完全不同
casts 不只是“让字段变数组”,它控制序列化/反序列化的默认形态,直接影响你操作数据的方式和边界。
-
'meta' => 'array':读取时转成 PHP 关联数组,写入时自动json_encode();但注意——空数组[]写入后是"[]",不是NULL,这会影响whereNull('meta')查询 -
'meta' => 'object':读取为stdClass,写入时也接受对象;但如果你传了关联数组进去,Laravel 会把它当成普通数组编码,结果仍是[],不是{} - 别混用:模型里设
array,但业务代码里又手动json_encode($obj)再赋值,会导致双重编码,存进去变成"{\"name\":\"foo\"}"(字符串里的 JSON)
whereJsonContains 查不到?先看字段是否真被 MySQL 当作 JSON 解析
这个方法表面简单,实际失败最多的原因是字段内容不合法,或者字段类型没对上。
- 执行
SELECT JSON_VALID(`meta`) FROM users LIMIT 1,返回 0 就说明字段内容不是有效 JSON(比如存了"null"字符串、或含非法转义) -
whereJsonContains('meta->roles', 'admin')要求roles是 JSON 数组;如果roles是字符串"admin",查不到 - MySQL 对大小写敏感:
whereJsonContains('meta->tags', 'PHP')不会匹配['php', 'laravel'];需要自己加LOWER()或改用正则 - 字段名带中划线或特殊字符?得用反引号:
whereJsonContains('meta->`user-id`', 123)
更新 JSON 字段局部值,别用 update() 全量覆盖,优先走 whereRaw + MySQL JSON 函数
想只改 meta.status,却用 $user->meta['status'] = 'done'; $user->save(),看似自然,实则风险高:并发写入可能丢数据,且无法原子更新。
- 安全做法是用原生 JSON 函数:
User::where('id', 1)->update(['meta' => DB::raw("JSON_SET(meta, '$.status', 'done')")]) -
JSON_SET会新增键,JSON_REPLACE只改已有键,JSON_REMOVE删键;选错函数会导致静默失败 - 注意:Eloquent 的
increment()、decrement()对 JSON 字段无效,必须手写JSON_SET+JSON_EXTRACT组合 - 如果非要用模型操作,确保
fill()后调用的是saveQuietly()或显式指定fillable,否则casts可能把整个 JSON 当字符串处理
SELECT meta FROM ... LIMIT 1 看一眼原始值,比查文档快。


















