php json_decode默认将所有数字转为float,需用(int)转换或json_bigint_as_string处理大整数,mysql写入前须显式指定pdo::param_int类型。

json_decode 默认把数字当 float,不是 int
PHP 的 json_decode 在解析 JSON 字符串时,对所有数字(包括 123、0、-456)统一转成 float 类型,哪怕 JSON 里写的是纯整数。这不是 bug,是 PHP 早期为兼容 IEEE 754 浮点精度做的设计选择。
常见错误现象:json_decode('{"id": 123}')['id'] === 123.0 返回 false;用 is_int() 判断失败;数据库插入时被当成浮点写入 DECIMAL 或触发类型警告。
- 如果 JSON 来源可控(比如自己生成),优先在 JSON 层就加引号变成字符串:
{"id": "123"},再手动(int)转换 - 若必须保留数字格式,且确定数值范围在 PHP
int安全范围内(通常 ≤ 253),可用intval()或强制类型转换:(int)$data['id'] - 注意:
intval("123.9") → 123,但intval("1e2") → 1—— 不要对含科学计数法的 JSON 数字直接intval
启用 JSON_BIGINT_AS_STRING 防止大整数变 0 或溢出
当 JSON 中出现超出 PHP int 表示范围的大整数(如 17 位以上订单号、MongoDB ObjectId),json_decode 默认会把它转成 float,再转 int 就可能变 0 或错值(例如 9223372036854775807 变成 9223372036854776000)。
正确做法是开启 JSON_BIGINT_AS_STRING 标志,让大整数原样作为字符串返回:
立即学习“PHP免费学习笔记(深入)”;
json_decode($json, false, 512, JSON_BIGINT_AS_STRING)
这样 {"order_id": 12345678901234567890} 中的 order_id 是字符串 "12345678901234567890",后续可安全传给 MySQL 的 BIGINT UNSIGNED 或调用 gmp_init() 处理。
- 该标志只影响「超出平台 int 范围」的数字,小整数仍按默认规则走 float
- PHP 7.4+ 支持,旧版本需自行正则预处理或改用
json_last_error()+ 重解析 - 别漏掉第 3 个参数(depth),设太小(如 1)会导致嵌套 JSON 解析失败
自定义递归转换:把所有数字字段批量转 int(慎用)
有些老系统要求整个数组里所有数字都变 int,又不想逐个字段写 (int)。可以写一个递归函数做后处理,但必须明确边界条件——否则会把 price、rate 这类本该是 float 的字段也截断。
推荐只对明确标识为整型用途的 key 名做转换,比如 id、status、count 等:
$intKeys = ['id', 'user_id', 'status', 'count', 'code'];<br>function castIntFields($data, $intKeys) {<br> if (!is_array($data)) return $data;<br> foreach ($data as $k => $v) {<br> if (is_numeric($v) && in_array($k, $intKeys)) {<br> $data[$k] = (int)$v;<br> } elseif (is_array($v)) {<br> $data[$k] = castIntFields($v, $intKeys);<br> }<br> }<br> return $data;<br>}
- 不要无差别
is_numeric() + (int)整个数组,会误伤"123.00"这种价格字符串 -
is_numeric("1e2")返回 true,但(int)"1e2"是 1 —— 若 JSON 可能含科学计数法,先filter_var($v, FILTER_VALIDATE_FLOAT)判断 - 性能上,深度嵌套时递归有开销,简单结构建议手动映射
MySQL 写入前检查:int 字段不能接受 float
即使 PHP 里用了 (int),如果数据库字段是 INT,而你传了 123.0(仍是 float 类型),PDO 或 mysqli 在严格模式下会报 SQLSTATE[HY000]: General error: 1366 Incorrect integer value。
真正保险的做法是在绑定参数时显式指定类型:
$stmt->bindValue(':id', $data['id'], PDO::PARAM_INT);
- 别依赖自动类型推断,尤其用
execute($array)批量绑定时,PDO::PARAM_STR是默认行为 - 如果用 Laravel Eloquent,确保模型里声明了
$casts = ['id' => 'int'],它会在存库前自动转换 - 注意:MySQL 8.0+ 对
STRICT_TRANS_TABLES模式更敏感,本地开发和线上环境 SQL mode 不一致时容易漏掉这个坑
最麻烦的情况是 JSON 里混着 int/float 语义但没约定字段名,这时候光靠解析逻辑救不了,得推动上游改 schema 或加 type hint 字段。










