JSON字段验证必须组合使用json和json_validate规则:json确保字符串可解析,json_validate校验内部结构;模型中避免提前cast,验证时须取原始字符串;需防范空字符串、null等合法JSON值导致的业务异常。

JSON字段结构验证必须用 json + json_validate 组合
单纯用 json 规则只能保证字段是合法 JSON 字符串,但无法校验内部结构。比如 {"name": "a", "age": "25"} 能过 json,但 age 应该是整数就漏了。Laravel 9.2+ 提供了 json_validate,它会把字符串 parse 后走一套独立的验证规则。
实操建议:
- 先用
json确保字段可解析,再用json_validate:*.name,required|string;*.age,required|integer|min:0校验结构 -
json_validate的规则写法类似数组验证,*表示遍历每个 JSON 对象(支持嵌套,如data.*.tags.*.id) - 如果 JSON 是单个对象(非数组),规则里去掉
*,直接写json_validate:name,required|string;age,required|integer - 注意:Laravel 9.2 之前没有
json_validate,得手写自定义验证器或用after回调做二次校验
模型属性强制转为 array 或 object 容易让验证失效
在模型里把 JSON 字段声明为 $casts = ['metadata' => 'array'] 很常见,但这会让请求数据在验证前就被 Laravel 自动解码——验证器拿到的是 PHP 数组,而 json 和 json_validate 规则只对原始字符串生效,直接跳过。
实操建议:
- 验证阶段保持字段为字符串,不要提前 cast;等验证通过后再在
store或update里手动json_decode($request->metadata, true) - 或者改用
$casts = ['metadata' => 'object'],但要注意json_validate仍要求输入是字符串,所以验证时还得从$request->input('metadata')取原始值,别用$request->metadata - 如果用了表单请求类(
FormRequest),务必在rules()里用$this->input('metadata')而非$this->metadata
前端传空字符串或 null 时 json 规则不报错
json 规则只检测是否为合法 JSON,而 ""、null、"null" 都是合法 JSON 值。用户清空表单后提交空字符串,验证器不会拦截,后续业务逻辑可能崩在 json_decode 后的 null 上。
Laravel Cloud(cloud.laravel.com)。此技能用于处理所有 Laravel Cloud 请求,包括搜索和读取数据。只要任务涉及 Laravel Cloud,即使用此技能。
实操建议:
- 加
required|string|min:2拦住空字符串(JSON 最短是{}或[],长度为 2) - 避免用
nullable,除非你明确接受null值并能在业务层安全处理 - 如果允许空对象,显式约定为
{},并在验证后统一 normalize:$data = json_decode($request->input('config'), true) ?: []; - 注意 MySQL 的 JSON 类型字段不接受
null字符串(即"null"),插入时会报错Invalid JSON text
MySQL 8.0+ 的 JSON 列校验和 Laravel 验证不是一回事
MySQL 的 JSON 列类型只保证语法合法,不校验字段名、类型、必填项。哪怕数据库建了 config JSON NOT NULL,也拦不住 {"user_id": "abc"} 这种类型错的数据入库。
实操建议:
- 数据库层只做基础兜底(比如设
NOT NULL、用生成列约束关键字段存在),别指望它替代应用层验证 - 如果要强约束,可用 MySQL 的
JSON_SCHEMA_VALID函数(8.0.17+),但 Laravel 迁移里没法直接写,得用DB::unprepared()手动建 CHECK 约束 - 更现实的做法:在模型的
booted()里加creating事件,对 JSON 字段做一次json_validate的复用校验,防止绕过 HTTP 请求直插数据
真正麻烦的是嵌套深、字段多的 JSON 结构——规则字符串容易写错位置,* 层级一多就漏匹配,调试时得打印 $request->input('payload') 看原始值,而不是只盯模型里的 cast 结果。










