PHP读写JSON文件需三步:读取→解析→回写;关键避坑点为编码、空文件、权限及JSON格式错误,须用file_get_contents+json_decode安全读取并检查返回值,修改后用json_encode配合JSON_UNESCAPED_UNICODE等选项格式化输出,并通过临时文件原子写入确保数据安全。

直接读写 JSON 文件会丢失格式和注释
PHP 的 json_encode() 和 json_decode() 默认不保留空格、换行或注释——哪怕原 JSON 文件是人工编辑的,一读一写就变成紧凑单行,协作时容易引发 diff 冲突。这不是 bug,是标准行为。
如果只是替换值且不关心格式,用 file_get_contents() + json_decode() + json_encode() 就够了;但若要保持缩进或兼容带注释的开发环境(比如某些配置文件),就得绕开纯 JSON 流程,改用字符串替换或专用库。
- 先用
file_get_contents('config.json')读取原始内容(不是解析后的数组) - 用正则或
str_replace()替换特定键的值,例如匹配"host": "old.example.com"→"host": "new.example.com" - 注意 JSON 字符串中的引号、转义和嵌套层级,简单
str_replace()只适用于扁平结构、键名唯一、值不含双引号的场景 - 更安全的做法:用
json_decode($json, true)解析,修改数组后,再用json_encode($data, JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT)输出带缩进的 JSON
修改嵌套字段时别漏掉引用传递
当你对解码后的数组做深层修改,比如 $config['database']['port'] = 5433,PHP 默认是值拷贝,但数组本身是引用语义——只要没用 array_merge() 或函数返回新数组,直接赋值就能生效。真正容易出错的是循环中修改子数组。
例如遍历 $config['services'] 并更新每个 service 的 timeout,写成 foreach ($config['services'] as $svc) { $svc['timeout'] = 30; } 是无效的,因为 $svc 是副本。
立即学习“PHP免费学习笔记(深入)”;
- 必须用引用:
foreach ($config['services'] as &$svc) { $svc['timeout'] = 30; } - 或者直接用键:
foreach (array_keys($config['services']) as $k) { $config['services'][$k]['timeout'] = 30; } - 修改完记得
unset($svc)(如果用了引用),避免后续意外修改
写入失败常因权限或路径问题,而非 JSON 格式错误
file_put_contents() 返回写入字节数,返回 false 表示失败,但错误原因往往不是 JSON 不合法——json_last_error() 在 json_encode() 阶段就该检查,写入失败更多是系统级问题。
- 确认目标目录可写:
is_writable(dirname('config.json')) - 检查路径是否绝对:相对路径依赖当前工作目录(
getcwd()),建议用__DIR__ . '/config.json' - 写入前先生成内容,再单独验证:
$json = json_encode($data); if ($json === false) { throw new Exception('JSON encode failed: ' . json_last_error_msg()); } - 用
file_put_contents($path, $json, LOCK_EX)加锁,避免并发写覆盖
中文字符乱码多半是没设 JSON_UNESCAPED_UNICODE
PHP 7.1+ 默认把 UTF-8 中文转成 \uXXXX 形式,看着像乱码,其实是合法 JSON,但可读性差,Git diff 也难看。
- 必须显式传 flag:
json_encode($data, JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT) -
JSON_UNESCAPED_UNICODE只影响字符串值,不影响键名(键名本来就不转义) - 如果用了旧版 PHP(



















