ThinkPHP模型中JSON字段默认不自动解析,需在模型中定义$json属性指定字段名;写入前需校验JSON合法性,查询依赖MySQL 5.7+原生函数,修改数组内容须整体赋值才能持久化。

ThinkPHP 模型里 json 类型字段不自动解析?
默认情况下,ThinkPHP 不会把数据库中 JSON 字段(如 MySQL 的 JSON 类型或 TEXT 字段存 JSON 字符串)自动转成 PHP 数组。哪怕你在数据库字段类型里写了 json,模型读出来还是字符串——这是最常被卡住的第一步。
解决方法是显式告诉模型这个字段要走 JSON 转换:
- 在模型类中定义
protected $json = ['extra', 'config'];,字段名必须和数据库列名完全一致 - 确保这些字段在数据库中确实是可解析的 JSON 字符串(不能是
null、空字符串或非法 JSON) - 如果字段值是
null,$model->extra会得到null,不是空数组;需要自己判断补默认值
用 json 属性写入时,数组自动转字符串但容易丢精度
当你给 json 类型字段赋值一个数组,ThinkPHP 会在写入前调用 json_encode(),但默认用的是 JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES,不带 JSON_PARTIAL_OUTPUT_ON_ERROR 或 JSON_INVALID_UTF8_SUBSTITUTE —— 遇到非法 UTF-8 字节或资源类型会直接报错或静默失败。
- 写入前先用
is_array($value) && json_encode($value, JSON_THROW_ON_ERROR)校验合法性 - 避免直接传
SimpleXMLElement、Resource、闭包等无法序列化的值 - 浮点数写入后可能因精度丢失(比如
3.141592653589793存成3.1415926535898),这不是 ThinkPHP 的问题,而是 PHPjson_encode对 double 的默认处理
whereJsonContains 和 whereJsonLength 在 MySQL 5.7+ 才可用
ThinkPHP 提供了几个 JSON 专用查询方法,但它们底层依赖 MySQL 原生 JSON 函数,低版本 MySQL 或 SQLite 直接报错。
立即学习“PHP免费学习笔记(深入)”;
-
whereJsonContains('options', '["admin"]')→ 底层生成JSON_CONTAINS(`options`, '"admin"'),要求字段是JSON类型且 MySQL ≥ 5.7 -
whereJsonLength('tags', '>', 2)→ 生成JSON_LENGTH(`tags`) > 2,同样不兼容旧版 - 如果用的是 TEXT 字段存 JSON 字符串,这些方法会失效,只能用
whereRaw+JSON_EXTRACT手写,或者改用字符串模糊匹配(不推荐)
模型获取 json 字段后修改数组内容,不调 save() 不会持久化
很多人以为 $user->profile['age'] = 28 后,下次 $user->save() 就能更新 JSON 字段,其实不会——ThinkPHP 不监听数组内部变化,只检测字段整体是否被重新赋值。
- 正确做法是:先取完整数组,改完再整体赋回去,例如
$data = $user->profile; $data['age'] = 28; $user->profile = $data; - 或者用
data()批量赋值:$user->data(['profile' => array_merge($user->profile, ['age' => 28])])->save(); - 别依赖魔术方法或实时引用,PHP 数组是值拷贝,模型里的
profile是个副本,改它不影响原始数据状态
JSON 字段看着省事,但一碰嵌套结构、部分更新、空值边界、跨版本兼容,就很容易漏掉转换时机或 SQL 能力限制。真要高频操作子字段,不如拆成独立关联表来得稳。











