PHP Unicode 編解碼筆記
記錄一下當接收到第三方返回的「中文漢字」是已經經過 Unicode 編碼時,如何還原成「正常漢字」去顯示的解法。
json_encode
首先可以先看一下 這篇提到的方式 ,僅限於「可掌握變數還是中文漢字時」使用:
$data = array(
'title' => '高級伴讀書僮',
'year' => 2021,
'do' => 'json_encode 編碼測試'
);
$json = json_encode($data);
echo "$json";
output
{“title”:"\u9ad8\u7d1a\u4f34\u8b80\u66f8\u50ee",“year”:2021,“do”:“json_encode \u7de8\u78bc\u6e2c\u8a66”}
// Use urlencode to workaround for json_encode without JSON_UNESCAPED_UNICODE
array_walk_recursive($data, function(&$value, $key) {
if(is_string($value)) {
$value = urlencode($value);
}
});
$json = urldecode(json_encode($data));
echo "$json";
output
{“title”:“高級伴讀書僮”,“year”:2021,“do”:“json_encode 編碼測試”}
概念上就是先把所有「中文字」做 urlencode
,在經過 json_encode
之後,再把 encode 結果執行 urldecode(整串 encode 結果)
還原裡面被 urlencode
編碼過的中文。
Unicode 編碼字符
假設發出請求後,第三方返回的文字已被編碼成 Unicode,如
\uxxxx\uxxxx\uxxxx...
我們該如何還原成可辨識的文字?在這邊先簡單帶過認識一下什麼是 Unicode 編碼:
Unicode
在表示一個 Unicode 的字元時,通常會用「U+」然後緊接著一組十六進位的數字來表示這一個字元。在基本多文種平面(英語:Basic Multilingual Plane,簡寫 BMP。又稱為「零號平面」、plane 0)裏的所有字元,要用四個數字(即 2 位元組,共 16 位元,例如 U+4AE0,共支援六萬多個字元);在零號平面以外的字元則需要使用五或六個數字。舊版的Unicode標準使用相近的標記方法,但卻有些微小差異:在Unicode 3.0 裏使用「U-」然後緊接著八個數字,而「U+」則必須隨後緊接著四個數字。 — 出處: Unicode - 維基百科
上網找了些資料查看到底怎麼還還原 Unicode 文字 (一開始接到只有 \uxxxx 根本沒頭緒阿…),不外乎提到上述
第一部份
講的情境,或是使用 mb_convert_encoding
、icov
等方式,去做到格式轉換;官方範例則是有提到:json_encode
要帶入參數 JSON_UNESCAPED_UNICODE
去跳脫編碼。
BUT!!! 想要還原已經變成 Unicode
的編碼文字,其實可簡單使用 json_decode
達成,透過 json_encode
我們知道中文會被編碼成:
$string = '高級伴讀書僮';
echo json_encode($string);
output
“\u9ad8\u7d1a\u4f34\u8b80\u66f8\u50ee”
注意到編碼結果帶有「雙引號」了嗎?因此我們可以透過此原理去解碼:
// 收到返回的內容已經被轉為 Unicode 了,想要轉成中文顯示
$responseText = "\u9ad8\u7d1a\u4f34\u8b80\u66f8\u50ee";
// 在編碼文字前後包上「雙引號」,並執行 `json_decode` 即可
$zhHant = json_decode('"'.$responseText.'"');
print_r($zhHant);
output
高級伴讀書僮
其中,補上雙引號在解碼這件事是不可省略的,否則解碼可能會失敗。
mb_convert_encoding
測試使用 mb_convert_encoding
做格式轉換 (
官方範例
)
$array = ["高級伴讀書僮", "編碼測試", '\u9ad8\u7d1a\u4f34\u8b80\u66f8\u50ee'];
// mb_convert_encoding ( array|string $string , string $to_encoding , array|string|null $from_encoding = null )
// mb_convert_encoding(文字, 編碼後的格式,來源文字格式/不確定可不給參數,或是給個 auto)
print_r(mb_convert_encoding($array, "utf8"));
根據上面程式碼,我就隨便假設有個使用場景為「讀入某個特殊編碼的檔案」,而你想把輸出全部轉為 utf8 時,可使用這個方法。
json_encode
測試使用 json_encode
觀察編碼的效果 (
官方範例
)
$stringArray = array('高級伴讀書僮');
echo "Normal: ", json_encode($stringArray), "\n";
echo "Tags: ", json_encode($stringArray, JSON_HEX_TAG), "\n";
echo "Apos: ", json_encode($stringArray, JSON_HEX_APOS), "\n";
echo "Quot: ", json_encode($stringArray, JSON_HEX_QUOT), "\n";
echo "Amp: ", json_encode($stringArray, JSON_HEX_AMP), "\n";
echo "Unicode: ", json_encode($stringArray, JSON_UNESCAPED_UNICODE), "\n";
echo "All: ", json_encode($stringArray, JSON_HEX_TAG | JSON_HEX_APOS | JSON_HEX_QUOT | JSON_HEX_AMP | JSON_UNESCAPED_UNICODE), "\n\n";
output
Normal: ["\u9ad8\u7d1a\u4f34\u8b80\u66f8\u50ee"]
Tags: ["\u9ad8\u7d1a\u4f34\u8b80\u66f8\u50ee"]
Apos: ["\u9ad8\u7d1a\u4f34\u8b80\u66f8\u50ee"]
Quot: ["\u9ad8\u7d1a\u4f34\u8b80\u66f8\u50ee"]
Amp: ["\u9ad8\u7d1a\u4f34\u8b80\u66f8\u50ee"]
Unicode: ["高級伴讀書僮"] // <-- JSON_UNESCAPED_UNICODE 宣告跳脫 Unicode 編碼
All: ["高級伴讀書僮"]
補充
後來搜尋關鍵字找到 這篇 ,借用作者的方法測試轉換的結果,理解到原來還有補滿 4 個字元(補零),會比較通用:
function unicode_to_utf8($unicode_str) {
$strArr = explode("\u",$unicode_str);
$unicode_str = "";
// 後面要補 0 防止 PHP 無法正常解碼
foreach ( $strArr as $row ) {
if ( empty($row) ) {
continue;
}
$unicode_str .= "\u" . str_pad($row,4,'0',STR_PAD_LEFT) ."";
}
$json = '{"str":"'.$unicode_str.'"}';
$arr = json_decode($json,true);
if(empty($arr)) return '';
return $arr['str'];
}
print_r(unicode_to_utf8("\u9ad8\u7d1a\u4f34\u8b80\u66f8\u50ee"));
print_r(unicode_to_utf8("\u41\u61")); // 未滿四個字元的解碼測試
output
高級伴讀書僮 Aa
小結
一開始收到 \uxxxx
這類文字編碼時,因為從來沒有遇到過,根本無從找起 (只能一段一段 \uxxxx 去 google 找),一開始還猜是不是簡體文字的編碼,還是什麼神秘的加密文字,怎麼 google 都沒辦法精準找到相關說明 (孤陋寡聞吶…),但我確信可以解碼,因為
這個網站
解的出來QQ;
最後試著用 json_encode
try 出可以得到一樣的編碼文字,也就是 Unicode 格式,再透過 json_decode
嘗試解碼,搞定。
把 google 過程找到的相關作法都稍微摸過,多看幾個與編碼相關,且經常使用的語法,如果以後在實務上遇到,也能比較清楚選用哪個方案著手處理編碼問題。
參考連結
文章作者: littlebookboy
永久鏈結: https://littlebookboy.github.io//2021/02/php-unicode-encode-and-decode/
許可協議: 署名-非商業性使用-相同方式共享 4.0 國際(CC BY-NC-SA 4.0)