記錄一下當接收到第三方返回的「中文漢字」是已經經過 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_encodingicov 等方式,去做到格式轉換;官方範例則是有提到: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://blog.genesu.me/2021/02/php-unicode-encode-and-decode/
許可協議: 署名-非商業性使用-相同方式共享 4.0 國際(CC BY-NC-SA 4.0)