laravel 的 json 类型字段通过 $casts 自动序列化/反序列化,需确保数据库使用 utf8mb4;迁移用 $table->json(),查询嵌套值推荐 wherejsoncontains();自定义访问器/修改器可增强控制但会覆盖 cast;务必验证前端 json 输入并确认生产环境数据库 json 支持级别。

模型字段声明为 json 类型后,laravel 自动处理序列化和反序列化
只要在模型的 $casts 数组里把字段设为 'meta' => 'json',Laravel 就会在写入数据库前自动 json_encode(),读取时自动 json_decode() 成 PHP 数组或对象。不用手动调用 json_encode() 或检查 is_String()。
常见错误现象:SQLSTATE[HY000]: General Error: 1366 Incorrect string value —— 这通常是因为数据库字段没设成 utf8mb4,导致 emoji 或某些 Unicode 字符存不进去。
- 确保 mysql 表字符集是
utf8mb4,排序规则是utf8mb4_unicode_ci - 迁移中用
$table->json('meta')(Laravel 5.2+),它会自动建JSON类型字段(MySQL 5.7+)或TEXT(低版本) - 如果用的是旧版 MySQL,
jsoncast 仍可用,但底层是TEXT,Laravel 会自行处理编解码
json 字段查询时不能直接用 where() 查嵌套值
比如 User::where('meta->theme', 'dark') 看起来能用,但实际只在 MySQL 5.7+ 的原生 JSON 字段上生效;sqlite 和旧版 MySQL 会报错或返回空。更稳妥的方式是先取出数据再 PHP 层过滤,或者用 whereJsonContains() / whereJsonDoesntContain()。
使用场景:查某个 JSON 字段是否包含特定键值对,比如用户设置里启用了通知:'notifications' => ['email' => true, 'sms' => false]。
-
whereJsonContains('meta', ['notifications->email' => true])—— 注意键路径写法,Laravel 会转成 MySQL 的JSON_CONTAINS - 嵌套太深(如
meta->settings->privacy->public)时,部分驱动不支持,建议拆到独立字段或改用 Eloquent 访问器 - 想查 JSON 数组长度?
whereRaw('JSON_LENGTH(meta->"$.roles") > 0')可行,但失去可移植性
用访问器(Accessor)和修改器(Mutator)控制 JSON 字段的读写逻辑
当默认的 json cast 不够用——比如要保证某个键始终存在、或对值做格式标准化(如强制转小写、补默认结构)——就得上访问器和修改器。
性能影响:每次读写都会触发 PHP 方法调用,比纯 cast 多一次函数开销,但通常可忽略;重点是别在访问器里做 DB 查询或 http 请求。
- 定义修改器:
public function setMetaAttribute($value) { $this->attributes['meta'] = json_encode(Array_merge(['version' => 1], (array) $value)); } - 定义访问器:
public function getMetaAttribute($value) { return json_decode($value, true) ?: ['version' => 1]; } - 注意:一旦定义了
setMetaAttribute,$casts里的'meta' => 'json'就失效了,两者不能共存
前端传来的 JSON 字符串,入库前务必验证结构
用户提交的 meta 字段可能是任意字符串,json_decode($input, true) 返回 NULL 时 Laravel 默认会存 NULL 或空字符串,容易埋坑。
容易踩的坑:request()->input('meta') 是字符串,但没校验就直接赋给模型,可能存入非法 JSON 导致后续 json_decode() 失败、页面炸开。
- 在 Form Request 中加验证:
'meta' => 'required|json'(Laravel 5.5+ 自带json规则) - 若需更细粒度校验(比如要求必须有
theme键),用required_with:meta+ 自定义规则 - 避免在控制器里写
json_decode(request('meta'), true) ?? []再赋值——这绕过了 cast 和验证,且 null 合并操作掩盖了原始错误
最常被忽略的是数据库兼容性:本地 SQLite 开发时一切正常,上线 MySQL 5.6 就发现 -> 操作符不识别,或者 JSON 函数报错。别只信本地表现,部署前确认目标环境的 JSON 支持级别。