JSON 是現代 Web 交換資料的標準格式。PHP 5.2+ 內建 json_encode/json_decode,但實際開發中常遇到中文轉義、大數字精度損失、巢狀深度限制、記憶體溢出等問題。本文系統梳理 PHP JSON 操作的核心機制、常見陷阱與最佳實踐。
<?php
// PHP 陣列/物件 → JSON
$arr = ['id' => 1, 'name' => '張三', 'tags' => ['php', 'json'], 'active' => true];
echo json_encode($arr);
// {"id":1,"name":"\u5f20\u4e09","tags":["php","json"],"active":true}
// JSON → PHP 物件 (預設)
$json = '{"id":1,"name":"張三"}';
$obj = json_decode($json);
echo $obj->name; // 張三
// JSON → PHP 關聯陣列 (第 2 參數 true)
$arr = json_decode($json, true);
echo $arr['name']; // 張三
| PHP 類型 | JSON 類型 | 說明 |
|---|---|---|
| 陣列 (索引 0,1,2...) | Array [] | 連續數字鍵 |
| 陣列 (關聯鍵) | Object {} | 字串鍵或非連續數字鍵 |
| 物件 (stdClass) | Object {} | public 屬性輸出 |
| 整數/浮點數 | Number | 科學計數法可能 |
| 字串 | String | UTF-8 必須 |
| true/false | true/false | |
| null | null | |
| resource/類別實例 | 不可序列化 | 需實作 JsonSerializable |
<?php
$data = ['msg' => '中文測試'];
// PHP 5.4+ 預設輸出 Unicode 轉義 (\uXXXX)
echo json_encode($data);
// {"msg":"\u4e2d\u6587\u6d4b\u8bd5"}
// PHP 5.4+ 使用 JSON_UNESCAPED_UNICODE 直接輸出中文
echo json_encode($data, JSON_UNESCAPED_UNICODE);
// {"msg":"中文測試"}
// PHP 5.3 及以下:需自行實作或升級 PHP
// 參考本文結尾的 polyfill 方案
最佳實踐:生產環境建議啟用 JSON_UNESCAPED_UNICODE,優點:
<?php
$options = JSON_UNESCAPED_UNICODE
| JSON_UNESCAPED_SLASHES // 不轉義 / (預設會轉 \/)
| JSON_HEX_TAG // < > 轉 \u003C \u003E (防 XSS)
| JSON_HEX_APOS // ' 轉 \u0027
| JSON_HEX_QUOT // " 轉 \u0022
| JSON_HEX_AMP // & 轉 \u0026
| JSON_PRESERVE_ZERO_FRACTION; // 1.0 保持輸出 1.0 而非 1
echo json_encode($data, $options);
<?php
$json = '{"order_id": 1234567890123456789, "amount": 99999999999999999.99}';
// 預設解碼:浮點數精度損失
$obj = json_decode($json);
var_dump($obj->order_id); // float(1.2345678901235E+18) 精度丟失!
var_dump($obj->amount); // float(1.0E+17) 精度丟失!
// 方案 1:JSON_BIGINT_AS_STRING (PHP 5.4+),大整數以字串返回
$obj = json_decode($json, false, 512, JSON_BIGINT_AS_STRING);
var_dump($obj->order_id); // string(19) "1234567890123456789" ✓
var_dump($obj->amount); // string(20) "99999999999999999.99" ✓
// 方案 2:搭配 true 返回關聯陣列
$arr = json_decode($json, true, 512, JSON_BIGINT_AS_STRING);
// $arr['order_id'] 為字串,需自行轉換或用 bcmath/gmp 運算
注意:JSON_BIGINT_AS_STRING 僅影響超出 PHP 整數範圍的數值(64 位系統為 > 2^63-1)。浮點數同樣以字串返回,避免精度損失。
<?php
// 預設最大深度 512,超過返回 null 並觸發 json_last_error()
$deep = '[' . str_repeat('[', 600) . ']' . str_repeat(']', 600) . ']';
$result = json_decode($deep);
var_dump($result); // null
var_dump(json_last_error()); // JSON_ERROR_DEPTH
var_dump(json_last_error_msg()); // "Maximum stack depth exceeded"
// 自訂深度 (PHP 5.3+ 第 3 參數)
$result = json_decode($deep, false, 1000); // 成功
// 完整錯誤檢查模式
function safeJsonDecode(string $json, bool $assoc = false, int $depth = 512, int $options = 0) {
$result = json_decode($json, $assoc, $depth, $options);
$err = json_last_error();
if ($err !== JSON_ERROR_NONE) {
throw new RuntimeException('JSON 解析失敗: ' . json_last_error_msg() . " (code: $err)");
}
return $result;
}
<?php
class User implements JsonSerializable {
private $id;
private $name;
private $password; // 敏感欄位不輸出
private $createdAt;
public function __construct(int $id, string $name, string $password, DateTime $createdAt) {
$this->id = $id; $this->name = $name; $this->password = $password; $this->createdAt = $createdAt;
}
public function jsonSerialize(): array {
return [
'id' => $this->id,
'name' => $this->name,
'created_at' => $this->createdAt->format('c'), // ISO 8601
// 故意不包含 password
];
}
}
$user = new User(1, '張三', 'secret123', new DateTime());
echo json_encode($user, JSON_UNESCAPED_UNICODE);
// {"id":1,"name":"張三","created_at":"2024-01-15T10:30:00+08:00"}
| 場景 | 問題 | 解決方案 |
|---|---|---|
| 超大 JSON 字串 (> 10MB) | 記憶體峰值高、解析慢 | 使用流式解析器 halaxa/json-machine、salsify/json-streaming-parser |
| 大量物件序列化 | CPU 佔用高 | 分批處理、json_encode 替代為 igbinary + msgpack (二進制) |
| 頻繁 API 回應 | 重複編碼開銷 | 快取序列化結果、使用 zend-json (C 擴充套件) |
<?php
require 'vendor/autoload.php';
use JsonMachine\JsonMachine;
$items = JsonMachine::fromFile('huge_data.json'); // 生成器,記憶體常數
foreach ($items as $key => $item) {
// 逐項處理,如寫入資料庫
if ($key > 10000) break; // 測試用
}
<?php
if (!defined('JSON_UNESCAPED_UNICODE')) {
define('JSON_UNESCAPED_UNICODE', 256);
function json_encode_utf8($value, $options = 0, $depth = 512) {
if (($options & JSON_UNESCAPED_UNICODE) && version_compare(PHP_VERSION, '5.4.0') < 0) {
// 簡易替代:先編碼,再將 \uXXXX 還原為 UTF-8 字元
$encoded = json_encode($value, $options & ~JSON_UNESCAPED_UNICODE, $depth);
return preg_replace_callback('/\\\\u([0-9a-fA-F]{4})/', function($m) {
return mb_convert_encoding(pack('H*', $m[1]), 'UTF-8', 'UTF-16BE');
}, $encoded);
}
return json_encode($value, $options, $depth);
}
}
| 錯誤代碼 | 常見原因 | 排查方向 |
|---|---|---|
| JSON_ERROR_SYNTAX | 尾隨逗號、單引號、未轉義控制字元、BOM | 用 jsonlint.com 驗證、檢查檔案編碼 UTF-8 無 BOM |
| JSON_ERROR_UTF8 | 字串含非 UTF-8 位元組 | mb_check_encoding($str, 'UTF-8')、iconv('GBK', 'UTF-8//IGNORE', $str) |
| JSON_ERROR_DEPTH | 巢狀過深 | 增加第 3 參數、重構資料結構 |
| JSON_ERROR_RECURSION | 物件循環引用 | 實作 JsonSerializable 避免循環 |
| JSON_ERROR_INF_OR_NAN | 浮點數 INF/NAN | 序列化前檢查 is_finite() |
熟練掌握 PHP JSON 的各項特性與選項,能讓你在 API 開發、配置管理、資料交換等場景中避開坑洞,編寫健壯高效的代碼。