我的个人博客站点会有最新更新,首更地址:https://www.veaxen.com/golang自定json序列化实现对非ascii字符进行转义.html
最近接手了一个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包提供了这样的支持。
查看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}"
显然是不符合预期的。