1. 程式人生 > >golang自定json序列化實現對非ASCII字元進行轉義

golang自定json序列化實現對非ASCII字元進行轉義

問題

最近接手了一個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沒有進行轉義

,這種問題在不同語言中都有存在,比如Python在序列化是,也跟php一樣,預設進行了這種轉義。

那麼問題來了,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}"

顯然是不符合預期的。