JSON 是現代 Web API 的標準資料交換格式。PHP 5.2+ 內建 json_encode() / json_decode(),本文深度解析其運作細節、陣列/物件轉換規則、Unicode 處理、常見錯誤與最佳實踐。
一、json_encode() 核心行為
基本轉換對照表
| PHP 型別 | JSON 型別 | 範例 |
| 陣列 (索引式,0 起始連續) | Array [] | [1,2,3] |
| 陣列 (關聯式/非連續索引) | Object {} | {"a":1,"b":2} |
| 物件 (public 屬性) | Object {} | {"name":"John"} |
| 字串 | String | "hello" |
| 整數/浮點數 | Number | 42, 3.14 |
| true/false | true/false | true |
| null | null | null |
| resource / 閉包 | ❌ 無法編碼 | 回傳 false 並觸發警告 |
關鍵選項 (bitmask)
<?php
$data = ['name' => '王小明', 'age' => 25, 'tags' => ['php', 'json']];
// JSON_UNESCAPED_UNICODE: 不轉義中文 (預設會變 \uxxxx)
echo json_encode($data, JSON_UNESCAPED_UNICODE);
// {"name":"王小明","age":25,"tags":["php","json"]}
// JSON_UNESCAPED_SLASHES: 不轉義 /
echo json_encode(['url' => 'https://example.com'], JSON_UNESCAPED_SLASHES);
// JSON_NUMERIC_CHECK: 數值字串轉數字
echo json_encode(['id' => '123', 'price' => '99.9'], JSON_NUMERIC_CHECK);
// {"id":123,"price":99.9}
// JSON_PRETTY_PRINT: 格式化輸出 (除錯用)
echo json_encode($data, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE);
/*
{
"name": "王小明",
"age": 25,
"tags": [
"php",
"json"
]
}
*/
// JSON_FORCE_OBJECT: 強制陣列輸出為物件
echo json_encode([1,2,3], JSON_FORCE_OBJECT);
// {"0":1,"1":2,"2":3}
// JSON_PRESERVE_ZERO_FRACTION: 保留浮點數 .0
echo json_encode(1.0, JSON_PRESERVE_ZERO_FRACTION); // 1.0
// JSON_THROW_ON_ERROR: 失敗拋出例外 (PHP 7.3+)
try {
json_encode($resource, JSON_THROW_ON_ERROR);
} catch (JsonException $e) {
// 處理錯誤
}
?>
二、索引陣列 vs 關聯陣列判斷邏輯
<?php
// 連續數字索引 0,1,2... -> JSON 陣列
json_encode(['a', 'b', 'c']); // ["a","b","c"]
json_encode([0=>'a', 1=>'b', 2=>'c']); // ["a","b","c"]
// 非連續、非 0 起始、字串鍵 -> JSON 物件
json_encode([1=>'a', 2=>'b']); // {"1":"a","2":"b"}
json_encode(['a'=>1, 'b'=>2]); // {"a":1,"b":2}
json_encode([0=>'a', 2=>'c']); // {"0":"a","2":"c"} (索引不連續)
// 強制輸出為陣列:array_values()
json_encode(array_values([1=>'a', 2=>'b'])); // ["a","b"]
?>
三、json_decode() 核心行為
<?php
$json = '{"name":"王小明","age":25,"skills":["PHP","JS"],"active":true}';
// 預設回傳 stdClass 物件
$obj = json_decode($json);
echo $obj->name; // 王小明
echo $obj->skills[0]; // PHP
// 第二參數 true -> 關聯陣列
$arr = json_decode($json, true);
echo $arr['name']; // 王小明
// 第三參數:最大深度 (預設 512)
$deep = json_decode($very_deep_json, true, 10);
// 第四參數:選項 (PHP 7.2+)
$opts = JSON_THROW_ON_ERROR | JSON_BIGINT_AS_STRING;
$arr = json_decode($json, true, 512, $opts);
?>
JSON_BIGINT_AS_STRING:避免大整數精度遺失
<?php
$json = '{"id": 12345678901234567890}';
// 預設:PHP 整數溢位轉浮點,精度遺失
var_dump(json_decode($json, true));
// ['id' => 1.2345678901235E+19]
// 保持字串
var_dump(json_decode($json, true, 512, JSON_BIGINT_AS_STRING));
// ['id' => '12345678901234567890']
?>
四、常見錯誤與除錯
1. JSON 語法錯誤 (json_last_error)
<?php
$bad_json = "{ 'name': 'test' }"; // 單引號不合法
$result = json_decode($bad_json);
var_dump($result); // null
// 檢查錯誤
switch (json_last_error()) {
case JSON_ERROR_NONE: echo '無錯誤'; break;
case JSON_ERROR_DEPTH: echo '超過最大深度'; break;
case JSON_ERROR_STATE_MISMATCH: echo '狀態不匹配'; break;
case JSON_ERROR_CTRL_CHAR: echo '控制字元錯誤'; break;
case JSON_ERROR_SYNTAX: echo '語法錯誤'; break;
case JSON_ERROR_UTF8: echo 'UTF-8 編碼錯誤'; break;
case JSON_ERROR_RECURSION: echo '遞迴參考'; break;
case JSON_ERROR_INF_OR_NAN: echo 'Inf/NaN'; break;
case JSON_ERROR_UNSUPPORTED_TYPE: echo '不支援型別'; break;
case JSON_ERROR_INVALID_PROPERTY_NAME: echo '屬性名無效'; break;
case JSON_ERROR_UTF16: echo 'UTF-16 編碼錯誤'; break;
}
// PHP 7.3+ 可用 JSON_THROW_ON_ERROR 直接拋出 JsonException
?>
2. UTF-8 編碼問題
<?php
// 輸入必須是 UTF-8
$str = '中文'; // 假設檔案是 UTF-8 編碼
json_encode($str); // 正常
// 若輸入是 Big5/GB2312,需先轉碼
$big5_str = iconv('BIG5', 'UTF-8', $big5_str);
json_encode($big5_str);
// 解碼時同理:json_decode 回傳 UTF-8 字串
$arr = json_decode($json, true);
// 若需輸出 Big5:iconv('UTF-8', 'BIG5', $arr['field'])
?>
3. 循環參考
<?php
$a = ['name' => 'A'];
$b = ['name' => 'B', 'ref' => &$a];
$a['ref'] = &$b;
json_encode($a); // 警告:Recursion detected,回傳 false
// 解決:實作 JsonSerializable 介面控制輸出
class Node implements JsonSerializable {
public $id;
public $children = [];
private $parent; // 不序列化
public function jsonSerialize() {
return ['id' => $this->id, 'children' => $this->children];
}
}
?>
五、效能優化
1. 避免重複編碼/解碼
<?php
// 壞:每次請求都 encode
echo json_encode($big_array);
// 好:快取編碼結果
$cache_key = 'json:' . md5(serialize($big_array));
$json = $redis->get($cache_key);
if (!$json) {
$json = json_encode($big_array, JSON_UNESCAPED_UNICODE);
$redis->setex($cache_key, 3600, $json);
}
echo $json;
?>
2. 大量資料串流輸出
<?php
// 避免一次編碼超大陣列佔用記憶體
function streamJsonArray($items) {
echo '[';
$first = true;
foreach ($items as $item) {
if (!$first) echo ',';
echo json_encode($item, JSON_UNESCAPED_UNICODE);
$first = false;
}
echo ']';
}
// 使用 Generator 記憶體友善
function getLargeDataset() {
for ($i = 0; $i < 1000000; $i++) {
yield ['id' => $i, 'value' => 'data'];
}
}
streamJsonArray(getLargeDataset());
?>
3. 擴充套件:更快的 JSON
| 擴充 | 特色 | 安裝 |
| json (內建) | 標準、穩定 | 預設 |
| jsond | 更嚴格 RFC 8259 相容 | pecl install jsond |
| simdjson | SIMD 加速,極快 (PHP 8.1+) | pecl install simdjson |
六、最佳實踐清單
- API 統一輸出 UTF-8 JSON:Header
Content-Type: application/json; charset=utf-8
- 永遠加上 JSON_UNESCAPED_UNICODE:中文可讀、體積更小
- 解碼指定 true 回傳陣列:
json_decode($json, true) 避免 stdClass 操作麻煩
- 大整數欄位用 JSON_BIGINT_AS_STRING:避免 ID 精度遺失
- 錯誤處理用 JSON_THROW_ON_ERROR:PHP 7.3+ 統一例外處理
- 不要手工拼 JSON 字串:永遠用
json_encode() 確保合法
- 資料庫 JSON 欄位:MySQL 5.7+ / PostgreSQL 9.4+ 原生支援 JSON 型別與索引
掌握 JSON 細節,能避免 API 整合時的大量除錯時間,也能寫出更健壯的資料交換介面。
- 上一篇文章: PHP 發送郵件兩大方案:內建 mail() vs SMTP 類別完整比較
- 下一篇文章: PHP + AJAX 實作無重整新聞留言系統完整教學
Copyright ©
程式碼之家 All Rights Reserved