go map 转 json 小记
这两天想用 Go 写一个 SDK ,又遇到一个坑,记录一下。需求背景是这样的,接口需要对参数进行升序排序然后转成 json
再 MD5
加密做签名对比。开发中遇到两个问题:
- Go 特殊字符转义
- map 转 json 只会 MD5 与 PHP 不一致
原始 Go 代码
params := make(map[string]string)
params["name"] = "test"
params["domain"] = "https://www.baidu.com?name=1&id=1"
data, err := json.Marshal(params)
if err != nil {
fmt.Println("json.Marshal failed:", err)
return
}
fmt.Println(string(data))
// 输出结果为 {"domain":"https://www.baidu.com?name=1\u0026id=1","name":"test"}
可以看到默认 encoding/json
是会对 map
排序以及特殊字符转义的。关于排序问题 Github
有很多人反馈给官方建议提供第二个参数设置是否继续排序,但是官方认为排序并不影响数据,所以建议并没被采纳。
代码修改为
params := make(map[string]string)
params["name"] = "test"
params["domain"] = "https://www.baidu.com?name=1&id=1"
// 排序
keys := make([]string, len(params))
i := 0
for k, _ := range params {
keys[i] = k
i++
}
sort.Strings(keys)
byteBuf := bytes.NewBuffer([]byte{})
encoder := json.NewEncoder(byteBuf)
encoder.SetEscapeHTML(false)
err := encoder.Encode(params)
if err != nil {
panic(err)
}
data := byteBuf.String()
fmt.Println(byteBuf.String())
// 输出结果为 {"domain":"https://www.baidu.com?name=1&id=1","name":"test"}
}
第一个问题得以解决。
第二个问题折腾的比较久,请教了对 Go
比较熟悉的开发以及在技术社区发帖。
最后才知道默认转换成 json
时,默认会在最后添加换行符 \n
,需要把 json
字符串最后面的换行符删掉才可以。
byteBuf := bytes.NewBuffer([]byte{})
encoder := json.NewEncoder(byteBuf)
encoder.SetEscapeHTML(false)
err := encoder.Encode(params)
if err != nil {
panic(err)
}
data := byteBuf.String()
fmt.Printf("%q", data) // 输出结果为 "{\"domain\":\"https://www.baidu.com?name=1&id=1\",\"name\":\"test\"}\n"
fmt.Printf("%q", strings.TrimRight(data, "\n")) // 输出结果为 "{\"domain\":\"https://www.baidu.com?name=1&id=1\",\"name\":\"test\"}"
最终代码
PHP 代码
$arr = [
'name'=>'test',
'domain' => 'https://www.baidu.com?name=1&id=1',
];
ksort($arr);
$json = json_encode($arr, JSON_UNESCAPED_SLASHES);
echo md5($json);
// 输出结果为 6fc68de881110444004b8f77e2a8fc73
Go 代码
package main
import (
"fmt"
"sort"
"encoding/json"
"crypto/md5"
"encoding/hex"
"bytes"
"strings"
)
func main() {
params := make(map[string]string)
params["name"] = "test"
params["domain"] = "https://www.baidu.com?name=1&id=1"
// 排序
keys := make([]string, len(params))
i := 0
for k, _ := range params {
keys[i] = k
i++
}
sort.Strings(keys)
byteBuf := bytes.NewBuffer([]byte{})
encoder := json.NewEncoder(byteBuf)
encoder.SetEscapeHTML(false)
err := encoder.Encode(params)
if err != nil {
panic(err)
}
data := byteBuf.String()
// fmt.Printf("%q", data)
h := md5.New()
h.Write([]byte(strings.TrimRight(data, "\n")))
fmt.Println(hex.EncodeToString(h.Sum(nil)))
}
参考资料
- https://github.com/golang/go/issues/27050
- https://github.com/golang/go/commit/6f25f1d4c901417af1da65e41992d71c30f64f8f
- https://golang.org/src/encoding/json/stream.go?s=6672:6714#L243
1 评论 在 "go map 转 json 小记"
最终代码里,这一段是多余的:
// 排序
keys := make([]string, len(params))
i := 0
for k, _ := range params {
keys[i] = k
i++
}
sort.Strings(keys)