golang自定json序列化實現對非ASCII字元進行轉義
阿新 • • 發佈:2018-12-19
問題
最近接手了一個Golang的專案,說實話,這個專案的坑點太多了,這裡就不吐槽了。在改這個專案的一個bug時,發現導致這個bug的其中一個原因是Golang的json序列化與PHP的json序列化結果是不同的,這裡舉一個簡單的例子。
對於PHP的json序列化:
$data = array(
"num"=>123456,
"key"=>"PHP是世界上最好用的語言"
);
$jsonStr = json_encode($data);
echo $jsonStr;
輸出的結果是:
{"num":123456,"key":"PHP\u662f\u4e16\u754c\u4e0a\u6700\u597d\u7528\u7684\u8bed\u8a00" }
對於Go的json序列化:
package main
import (
"encoding/json"
"fmt"
)
func main() {
data := map[string]interface{}{
"num":123456,
"key":"Go是世界上最好用的語言",
}
jsonData, _ :=json.Marshal(data)
fmt.Println(string(jsonData))
}
輸出結果是:
{"key":"Go是世界上最好用的語言","num":123456}
很明顯可以看出來,php在序列化時,預設對Unicode編碼的字元進行了轉義,而Go沒有進行轉義
那麼問題來了,php能不能不要進行轉義,答案是YES。只需要:
//PHP 5.4.0起才有JSON_UNESCAPED_UNICODE這個選項
$jsonStr = json_encode($data, JSON_UNESCAPED_UNICODE);
如果能這樣改,讓Go和PHP產生的json一樣,那個當然是很開心的一件事,但是現在考慮各種各樣的問題,有時候我們需要讓Go來進行轉義(例如用Go重構PHP專案,又要考慮相容以前的舊客戶端的解析),但很可惜,Go並沒有像PHP一樣提供類似的選項讓我們決定要不要進行轉義。解決的辦法是自定義Go的json序列化過程,Go的json包提供了這樣的支援
Go自定義Json序列化
檢視json包文件,我們可以發現有關於自定義序列化的例子,當執行json序列化時,如果對應的型別實現了Marshaler介面:
type Marshaler interface {
MarshalJSON() ([]byte, error)
}
那麼就會執行其MarshalJSON方法,並將返回的位元組陣列作為該值的序列化結果。
回到我們的問題上來,我們可以通過這種自定義序列化來解決我們的問題,實現對非ASCII編碼的字元進行轉義,具體程式碼如下。
package main
import (
"encoding/json"
"fmt"
"strconv"
)
type escapeString string
func (esc escapeString) MarshalJSON() ([]byte, error) {
return []byte(strconv.QuoteToASCII(string(esc))), nil
}
func main() {
data := map[string]interface{}{
"num":123456,
"key":escapeString("Go是世界上最好用的語言"),
}
jsonData, _ :=json.Marshal(data)
fmt.Println(string(jsonData))
}
strconv.QuoteToASCII()
方法將非ASCII編碼的字元進行了轉義,並返回字串。
那麼既然有這個方法,還需要自定義序列化過程嗎?直接對正常序列化的結果進行轉義不就好了嗎?
data := map[string]interface{}{
"num":123456,
"key":"Go是世界上最好用的語言",
}
jsonData, _ :=json.Marshal(data)
fmt.Println(strconv.QuoteToASCII(string(jsonData)))
這樣得到的結果是這樣的:
"{\"key\":\"Go\u662f\u4e16\u754c\u4e0a\u6700\u597d\u7528\u7684\u8bed\u8a00\",\"num\":123456}"
顯然是不符合預期的。