go map 转 json 小记

这两天想用 Go 写一个 SDK ,又遇到一个坑,记录一下。需求背景是这样的,接口需要对参数进行升序排序然后转成 jsonMD5 加密做签名对比。开发中遇到两个问题:

  1. Go 特殊字符转义
  2. 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 小记"

提醒
avatar
排序:   最新 | 最旧 | 得票最多
yi.he
游客

最终代码里,这一段是多余的:
// 排序
keys := make([]string, len(params))
i := 0
for k, _ := range params {
keys[i] = k
i++
}
sort.Strings(keys)