PHP7.2原生json_decode不支持流式解析大JSON,需手动实现状态机逐字符解析,注意处理BOM、换行符、UTF-8多字节截断及字符串内嵌套符号。

PHP7.2 原生 json_decode() 无法流式解析大 JSON
直接用 json_decode(file_get_contents($path)) 读取几百 MB 的 JSON 文件,PHP 进程会瞬间吃光内存,触发 Fatal error: Allowed memory size exhausted。PHP7.2 没有内置的流式 JSON 解析器,json_decode() 必须一次性加载完整字符串,这是根本限制,绕不开。
用 JSONStream 类手动实现“边读边解析”
社区常用方案是基于 JsonStreamingParser(如 saulius/json-streaming-parser),但它依赖 PHP7.3+ 的 iterable 类型声明,在 PHP7.2 下会报 ParseError: syntax error, unexpected 'iterable'。必须降级使用兼容 PHP7.2 的 fork 或轻量替代——推荐改用 JSONStream(非 composer 官方包,而是指手写状态机式逐字符解析)。
实操建议:
- 用
fopen($path, 'r')打开文件,配合fgets()或fread()分块读取(如每次 8192 字节),避免单次file_get_contents - 维护一个括号/引号嵌套计数器(
$depth),遇到{加 1、}减 1;遇到"切换是否在字符串内(跳过字符串里的{/}) - 当
$depth === 0且当前行末尾是}或],说明一个顶层对象/数组已完整读入,此时对缓存字符串调用json_decode($chunk, true) - 注意:JSON 不允许注释、尾部逗号,确保源文件格式合规,否则
json_last_error()会返回JSON_ERROR_SYNTAX
处理数组型大 JSON(如 [{"id":1}, {"id":2}, ...])最简单
如果 JSON 是顶层为数组、每个元素是独立对象(常见日志或导出数据),无需复杂状态机,可按行或按逗号分割 + 简单括号匹配即可分段。
立即学习“PHP免费学习笔记(深入)”;
示例逻辑片段:
$fp = fopen('big.json', 'r');
$buffer = '';
$depth = 0;
$inString = false;
$escape = false;
while (($char = fgetc($fp)) !== false) {
if ($escape) {
$escape = false;
$buffer .= $char;
continue;
}
if ($char === '\') {
$escape = true;
$buffer .= $char;
continue;
}
if ($char === '"' && !$inString) {
$inString = true;
} elseif ($char === '"' && $inString && !isset($buffer[-1]) || $buffer[-1] !== '\') {
$inString = false;
}
$buffer .= $char;
if (!$inString) {
if ($char === '{') $depth++;
if ($char === '}') $depth--;
if ($depth === 0 && strlen($buffer) > 2) {
$obj = json_decode($buffer, true);
if (json_last_error() === JSON_ERROR_NONE) {
// 处理 $obj
process_item($obj);
}
$buffer = '';
}
}
}
别忽略 UTF-8 BOM 和换行符干扰
Windows 生成的 JSON 文件可能带 BOM,或混合
换行,导致开头解析失败或 json_decode() 返回 null 而不报错。必须在读取前剥离 BOM,并统一换行符处理。
建议步骤:
- 打开文件后立即用
fseek($fp, 0)回到开头,读 3 字节判断是否为 BOM,是则fseek($fp, 3) - 避免依赖
trim()清空空白——它不处理,应显式替换str_replace([" ", " "], " ", $buffer) - 若 JSON 中含 base64 字符串或长文本,确保
$buffer累积时不截断多字节 UTF-8 字符(fread()按字节读,可能切在中间)。稳妥做法是用mb_substr()配合mb_strlen()控制边界,或改用stream_get_line($fp, 0, " ")按行读(前提是每行一个 JSON 对象)
json_decode() 就静默失败。



















