PHP json_encode() 遇 float 变 null 的根本原因是其默认拒绝非标浮点数(NaN、INF、-INF),遇之则整个字段置为 null 而不报错;常见诱因包括计算溢出、除零及未清洗的外部数据。

PHP json_encode() 遇到 float 变 null 的根本原因
这不是 PHP 的 bug,而是 json_encode() 对非标浮点数(如 NaN、INF、-INF)的默认拒绝策略。当某个字段值是 float 类型但实际为 INF 或 NAN 时,json_encode() 直接返回 null(注意:不是字符串 "null",而是整个字段被置为 null),且不报错、不警告。
常见诱因包括:
- 计算结果溢出(如
1e300 * 1e300→INF) - 除零(如
1.0 / 0.0→INF,0.0 / 0.0→NAN) - 从数据库或外部接口读取了未清洗的数值(如 MySQL 的
DOUBLE字段存了inf字面量) - 使用了
unserialize()后未校验浮点字段,再传给json_encode()
如何快速定位哪个字段导致 json_encode 返回 null
别靠猜。用 JSON_PARTIAL_OUTPUT_ON_ERROR + 逐层检测是最稳的路径:
- 先启用容错模式:
json_encode($data, JSON_PARTIAL_OUTPUT_ON_ERROR)—— 它会让非法浮点转成字符串"null"而非让整个 encode 失败 - 再配合递归检查:
is_finite($val)是核心判断,它对INF和NAN均返回false - 写个简单遍历函数,遇到
float且!is_finite($val)就记录路径(如$data['items'][2]['price'])
示例片段:
立即学习“PHP免费学习笔记(深入)”;
function findInvalidFloat($data, $path = '') {
if (is_float($data) && !is_finite($data)) {
echo "Invalid float at {$path}: " . var_export($data, true) . "\n";
} elseif (is_array($data) || is_object($data)) {
foreach ($data as $k => $v) {
$nextPath = $path ? "{$path}.{$k}" : $k;
findInvalidFloat($v, $nextPath);
}
}
}序列化顺序影响 JSON 输出?不,但类型转换时机很关键
PHP 中「先序列化再 json_encode」和「直接 json_encode」行为不同,本质是类型丢失时机问题:
-
serialize()会把INF/NAN存为字符串s:3:"INF"或s:3:"NAN",再unserialize()时还原为原浮点值 —— 这时才真正触发json_encode()的拦截 - 而直接
json_encode()不经过序列化,只看当前变量的运行时类型 - 所以问题不在“顺序”,而在你是否在某个环节(比如缓存反序列化、ORM 属性赋值)无意中引入了非法浮点
特别注意:PDO 默认将 MySQL 的 inf 字面量映射为 PHP INF,而不是字符串。开启 PDO::ATTR_EMULATE_PREPARES 或用 CAST(col AS CHAR) 可规避。
安全替换非法浮点的推荐做法
不要全局用 str_replace() 或正则处理 JSON 字符串 —— 易破坏合法内容。应在数据进 JSON 前做净化:
- 对单个值:
is_finite($x) ? $x : null或is_finite($x) ? $x : 0.0(按业务语义选) - 对数组/对象:用
array_walk_recursive()+ 回调函数统一处理 - 更健壮的做法:封装一个
json_safe_encode(),内部先 deep-copy 并 sanitize 所有浮点,再调用原生json_encode()
记住:PHP 8.1+ 支持 JSON_INVALID_UTF8_IGNORE,但它对 INF/NAN 无效 —— 这类浮点问题必须前置处理,没有捷径。











