json_encode 是 PHP 處理 JSON 的核心函數,但其只支持 UTF-8 編碼。在 GBK/GB2312 編碼的項目中,直接使用會導致中文變為 null 或產生 Unicode 轉義序列(如 \u4e2d\u6587)。本文提供從根因分析到多種解決方案的完整指南。
<?php
// 文件編碼:GBK
$arr = ['name' => '中文', 'value' => 'test'];
echo json_encode($arr);
// 輸出: {"name":null,"value":"test"}
?>
原因:json_encode 遇到非 UTF-8 字符直接返回 null(PHP 5.5+)或報錯(舊版本)。
<?php
// 文件編碼:UTF-8
$arr = ['name' => '中文', 'value' => 'test'];
echo json_encode($arr);
// 輸出: {"name":"\u4e2d\u6587","value":"test"}
?>
原因:默認啟用 JSON_UNESCAPED_UNICODE 選項(PHP 5.4+)可禁用轉義。
<?php
$data = "中文內容";
// GBK 轉 UTF-8,IGNORE 忽略無法轉換字符
$utf8 = iconv('GBK', 'UTF-8//IGNORE', $data);
// 或 GB2312
$utf8 = iconv('GB2312', 'UTF-8//IGNORE', $data);
echo json_encode(['content' => $utf8]);
?>
<?php
/**
* 將任意變量從 GBK/GB2312 遞歸轉換為 UTF-8
* @param mixed $data
* @param string $fromEncoding 源編碼
* @return mixed
*/
function toUtf8(mixed $data, string $fromEncoding = 'GBK'): mixed {
if (is_array($data)) {
$result = [];
foreach ($data as $key => $value) {
$result[toUtf8($key, $fromEncoding)] = toUtf8($value, $fromEncoding);
}
return $result;
} elseif (is_object($data)) {
$vars = get_object_vars($data);
foreach ($vars as $key => $value) {
$data->$key = toUtf8($value, $fromEncoding);
}
return $data;
} elseif (is_string($data)) {
return iconv($fromEncoding, 'UTF-8//IGNORE', $data);
}
return $data;
}
// 使用
$gbkData = ['name' => '測試', 'items' => ['項目一', '項目二']];
$utf8Data = toUtf8($gbkData);
echo json_encode($utf8Data, JSON_UNESCAPED_UNICODE);
// 輸出: {"name":"測試","items":["項目一","項目二"]}
?>
利用 urlencode 將中文編碼為 ASCII 安全字符,json_encode 後再 urldecode 還原。
<?php
function jsonEncodeChinese(mixed $data): string {
if (is_array($data)) {
foreach ($data as $key => $value) {
$data[$key] = jsonEncodeChinese($value);
}
return $data;
} elseif (is_string($data)) {
return urlencode($data);
}
return $data;
}
function jsonDecodeChinese(string $json): mixed {
$data = json_decode($json, true);
return urldecode($data);
}
// 使用
$arr = ['name' => '中文測試', 'tags' => ['標籤一', '標籤二']];
$encoded = jsonEncodeChinese($arr);
$json = json_encode($encoded);
echo urldecode($json);
// 輸出: {"name":"中文測試","tags":["標籤一","標籤二"]}
?>
優點:不依賴 iconv 擴展、處理混合編碼更健壯、性能優於 iconv。
缺點:字符串長度增加約 3 倍(每個中文變 9 字符 %E4%B8%AD),大數據量需注意內存。
<?php
$arr = ['name' => '中文', 'value' => 'test'];
// 關鍵選項:JSON_UNESCAPED_UNICODE (256)
echo json_encode($arr, JSON_UNESCAPED_UNICODE);
// 輸出: {"name":"中文","value":"test"}
// 組合常用選項
$options = JSON_UNESCAPED_UNICODE
| JSON_UNESCAPED_SLASHES // 不轉義 /
| JSON_HEX_TAG // 轉義 < > 防 XSS
| JSON_HEX_APOS // 轉義 '
| JSON_HEX_QUOT // 轉義 "
| JSON_HEX_AMP; // 轉義 &
echo json_encode($arr, $options);
?>
<?php
$arr = ['name' => '中文', 'data' => [1, 2, 3]];
try {
$json = json_encode($arr, JSON_UNESCAPED_UNICODE | JSON_THROW_ON_ERROR);
echo $json;
} catch (JsonException $e) {
error_log("JSON 編碼失敗: " . $e->getMessage());
// 降級處理
echo json_encode(['error' => 'encoding_failed']);
}
?>
| 方案 | 適用場景 | PHP 版本 | 性能 | 依賴 | 推薦指數 |
|---|---|---|---|---|---|
| iconv 轉 UTF-8 | GBK/GB2312 老項目 | 4.0.5+ | 中 | iconv 擴展 | ⭐⭐⭐⭐ |
| urlencode 技巧 | 混合編碼、無 iconv 環境 | 4.0+ | 高 | 無 | ⭐⭐⭐⭐⭐ |
| JSON_UNESCAPED_UNICODE | UTF-8 新項目 | 5.4+ | 最高 | 無 | ⭐⭐⭐⭐⭐ |
| JSON_THROW_ON_ERROR | 現代異常處理 | 8.1+ | 最高 | 無 | ⭐⭐⭐⭐ |
<?php
class JsonService {
private static $defaultOptions = JSON_UNESCAPED_UNICODE
| JSON_UNESCAPED_SLASHES
| JSON_HEX_TAG
| JSON_HEX_APOS
| JSON_HEX_QUOT
| JSON_HEX_AMP;
public static function encode(mixed $data, int $options = 0, bool $autoUtf8 = true): string {
if ($autoUtf8 && !self::isUtf8($data)) {
$data = self::convertToUtf8($data);
}
$finalOptions = self::$defaultOptions | $options;
if (PHP_VERSION_ID >= 80100) {
$finalOptions |= JSON_THROW_ON_ERROR;
return json_encode($data, $finalOptions);
}
$json = json_encode($data, $finalOptions);
if (json_last_error() !== JSON_ERROR_NONE) {
throw new RuntimeException('JSON 編碼失敗: ' . json_last_error_msg());
}
return $json;
}
public static function decode(string $json, bool $assoc = true, int $depth = 512): mixed {
if (PHP_VERSION_ID >= 80100) {
return json_decode($json, $assoc, $depth, JSON_THROW_ON_ERROR);
}
$result = json_decode($json, $assoc, $depth);
if (json_last_error() !== JSON_ERROR_NONE) {
throw new RuntimeException('JSON 解碼失敗: ' . json_last_error_msg());
}
return $result;
}
private static function isUtf8(mixed $data): bool {
if (is_string($data)) {
return mb_check_encoding($data, 'UTF-8');
}
if (is_array($data)) {
foreach ($data as $v) {
if (!self::isUtf8($v)) return false;
}
}
return true;
}
private static function convertToUtf8(mixed $data): mixed {
// 優先嘗試檢測編碼,常見 GBK/GB2312
$encodings = ['GBK', 'GB2312', 'BIG5'];
foreach ($encodings as $enc) {
if (self::tryConvert($data, $enc)) {
return self::doConvert($data, $enc);
}
}
// 降級:urlencode 技巧
return self::urlencodeRecursive($data);
}
private static function tryConvert(mixed $data, string $from): bool {
if (is_string($data)) {
@iconv($from, 'UTF-8', $data);
return iconv_get_last_error() === ICONV_ERR_SUCCESS;
}
return true;
}
private static function doConvert(mixed $data, string $from): mixed {
if (is_array($data)) {
return array_map(fn($v) => self::doConvert($v, $from), $data);
}
if (is_string($data)) {
return iconv($from, 'UTF-8//IGNORE', $data);
}
return $data;
}
private static function urlencodeRecursive(mixed $data): mixed {
if (is_array($data)) {
return array_map([self::class, 'urlencodeRecursive'], $data);
}
return is_string($data) ? urlencode($data) : $data;
}
}
// 使用
echo JsonService::encode(['msg' => '你好世界', 'code' => 200]);
// {"msg":"你好世界","code":200}
?>
charset=utf8mb4,MySQLi 執行 SET NAMES utf8mb4<meta charset="UTF-8">Content-Type: application/json; charset=utf-8json_encode,防止注入與語法錯誤徹底解決 json_encode 中文問題的關鍵在於:源頭統一 UTF-8。老項目若無法全面轉碼,請使用 JsonService 封裝類,在邊界處統一處理編碼轉換,業務代碼保持純淨。