PHP json_encode() 預設將非 ASCII 字符 (含中文) 轉義為 \uXXXX 格式。本文提供從 PHP 5.3 到 8.0+ 的完整解決方案,含原生選項、Polyfill 實現、效能對比。
<?php
$data = ['message' => '你好世界', 'status' => '成功'];
echo json_encode($data);
// 輸出: {"message":"\u4f60\u597d\u4e16\u754c","status":"\u6210\u529f"}
// 調試困難、日誌不可讀、傳輸體積略大
<?php
// PHP 5.4.0+ 支援
echo json_encode($data, JSON_UNESCAPED_UNICODE);
// 輸出: {"message":"你好世界","status":"成功"}
// 組合選項
$options = JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES | JSON_NUMERIC_CHECK;
echo json_encode($data, $options);
| 常量 | PHP 版本 | 作用 |
|---|---|---|
JSON_UNESCAPED_UNICODE | 5.4+ | 不轉義 Unicode 字符 (中文可讀) |
JSON_UNESCAPED_SLASHES | 5.4+ | 不轉義 / (URL 更乾淨) |
JSON_NUMERIC_CHECK | 5.3.3+ | 數字字符串轉數字類型 |
JSON_PRETTY_PRINT | 5.4+ | 格式化輸出 (調試用) |
JSON_PRESERVE_ZERO_FRACTION | 5.6.6+ | 保留浮點數 .0 (如 1.0 不變 1) |
JSON_THROW_ON_ERROR | 7.3+ | 錯誤拋出異常而非返回 false |
<?php
class JsonEncoder {
public static function encode(mixed $value, int $flags = 0): string {
// 如果支援原生選項,直接使用
if (defined('JSON_UNESCAPED_UNICODE') && ($flags & JSON_UNESCAPED_UNICODE)) {
return json_encode($value, $flags);
}
return self::encodeRecursive($value, $flags);
}
private static function encodeRecursive(mixed $value, int $flags): string {
if (is_null($value)) return 'null';
if (is_bool($value)) return $value ? 'true' : 'false';
if (is_int($value) || is_float($value)) return (string)$value;
if (is_string($value)) return self::encodeString($value);
if (is_array($value)) return self::encodeArray($value, $flags);
if (is_object($value)) return self::encodeObject($value, $flags);
throw new Exception('不支援的類型: ' . gettype($value));
}
private static function encodeString(string $s): string {
// 關鍵:不轉義 Unicode,僅轉義必要字符
$replace = [
'"' => '\"',
'\\' => '\\\\',
"\b" => '\b',
"\f" => '\f',
"\n" => '\n',
"\r" => '\r',
"\t" => '\t',
];
return '"' . strtr($s, $replace) . '"';
}
private static function encodeArray(array $arr, int $flags): string {
$isList = array_keys($arr) === range(0, count($arr) - 1);
$parts = [];
if ($isList) {
foreach ($arr as $v) {
$parts[] = self::encodeRecursive($v, $flags);
}
return '[' . implode(',', $parts) . ']';
} else {
foreach ($arr as $k => $v) {
$parts[] = self::encodeString((string)$k) . ':' . self::encodeRecursive($v, $flags);
}
return '{' . implode(',', $parts) . '}';
}
}
private static function encodeObject(object $obj, int $flags): string {
// 簡化:轉為關聯數組
return self::encodeArray((array)$obj, $flags);
}
}
// 使用
echo JsonEncoder::encode($data, JSON_UNESCAPED_UNICODE);
// {"message":"你好世界","status":"成功"}
composer require "symfony/polyfill-php80" // 含 JSON 相關 polyfill
// 或
composer require "laminas/laminas-json" // 更完整的 JSON 處理
<?php
$data = array_fill(0, 10000, ['text' => '中文測試內容', 'id' => 123, 'active' => true]);
// 原生 json_encode (無選項)
$start = microtime(true);
for ($i = 0; $i < 100; $i++) json_encode($data);
echo "原生無選項: " . (microtime(true) - $start) . "s\n"; // ~0.12s
// 原生 + UNESCAPED_UNICODE
$start = microtime(true);
for ($i = 0; $i < 100; $i++) json_encode($data, JSON_UNESCAPED_UNICODE);
echo "原生+UNESCAPED: " . (microtime(true) - $start) . "s\n"; // ~0.13s
// Polyfill
$start = microtime(true);
for ($i = 0; $i < 100; $i++) JsonEncoder::encode($data);
echo "Polyfill: " . (microtime(true) - $start) . "s\n"; // ~0.85s (慢 6-7 倍)
<?php
class ApiResponse {
public int $code;
public string $message;
public mixed $data;
private string $internalToken; // 不想輸出
public function jsonSerialize(): array {
return [
'code' => $this->code,
'message' => $this->message,
'data' => $this->data,
// internalToken 自動排除
];
}
}
$resp = new ApiResponse();
$resp->code = 200;
$resp->message = '操作成功';
$resp->data = ['user_id' => 123];
$resp->internalToken = 'secret';
echo json_encode($resp, JSON_UNESCAPED_UNICODE);
// {"code":200,"message":"操作成功","data":{"user_id":123}}
| 現象 | 原因 | 解決 |
|---|---|---|
| 中文仍被轉義 | PHP < 5.4 或未加選項 | 升級 PHP 或用 Polyfill |
| 輸出 null | 資料包含非 UTF-8 編碼 | mb_convert_encoding($data, 'UTF-8', 'auto') |
| 深度遞歸錯誤 | 循環引用對象 | 實現 JsonSerializable 或斷開循環 |
| 浮點數精度問題 | JSON 數字精度限制 | 大數值用字符串傳輸 |
JSON_UNESCAPED_UNICODE,零成本、最高效symfony/polyfill-php80,無縫升級JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHESJSON_PRETTY_PRINT 便於人工閱讀中文不轉義是 JSON API 友好性的基礎配置。現代 PHP 只需一個常量即可解決,舊版本有成熟 Polyfill。建立統一的 JSON 編碼策略,能避免調試地獄、減少傳輸體積、提升跨語言互操作體驗。