问题
最近在做iris框架切换fiber框架时,碰到一个坑,fiber看注释说是默认使用官方标准库的json解析库,iris默认也是使用官方标准库的json解析库,所以切换后没有关注这方面,后面业务研发人员反馈,框架切换后,解析json有问题,以前能解析出来,现在解析不出来了
// 这个结构体,注意看是[]uint8
type Param struct {
InputIdList []uint8 `json:"inputIdList"`
}
// fiber里解析也很容易
app.Post("/aa", func(ctx *fiber.Ctx) error {
var param Param
err := ctx.BodyParser(¶m)
if err != nil {
//实际代码跑到这里,提示json: cannot unmarshal into Go value of type uint8
return err
}
// 其他业务逻辑
// xxxx
return nil
}
后面跟踪进去发现fiber里使用的是go-json
最后解决方案也很简单,在配置里配置使用官方标准库
app := fiber.New(fiber.Config{
// json 编码库
JSONEncoder: json.Marshal,
// json 解码库
JSONDecoder: json.Unmarshal,
})
然后解析就正常了,然后再看编码,发现结构体里含有[]uint8,输出会转成[]byte,在base64编码,如果用刚才param的结构体,输出就是
{
"inputIdList": "AQI="
}
我们期望的结果是
{
"inputIdList": [1,2]
}
后面上网搜索了一下,发现大概都是这样的解决方案
import "fmt"
import "encoding/json"
import "strings"
type Test struct {
Name string
Array []uint8
}
func (t *Test) MarshalJSON() ([]byte, error) {
var array string
if t.Array == nil {
array = "null"
} else {
array = strings.Join(strings.Fields(fmt.Sprintf("%d", t.Array)), ",")
}
jsonResult := fmt.Sprintf(`{"Name":%q,"Array":%s}`, t.Name, array)
return []byte(jsonResult), nil
}
func main() {
t := &Test{"Go", []uint8{'h', 'e', 'l', 'l', 'o'}}
m, err := json.Marshal(t)
if err != nil {
fmt.Println(err)
}
fmt.Printf("%s", m) // {"Name":"Go","Array":[104,101,108,108,111]}
}
其实就是结构体序列化的时候可以自定义序列化的方法,不使用官方的,那按这么说,我是不是可以对某个结构按我定义的方法去编码和解码呢?
type Param struct {
InputIdList []uint8 `json:"inputIdList"`
}
// json编码
func (p *Param) MarshalJSON() ([]byte, error) {
var array string
if p.InputIdList == nil {
array = "null"
} else {
array = strings.Join(strings.Fields(fmt.Sprintf("%d", p.InputIdList)), ",")
}
jsonResult := fmt.Sprintf(`{"inputIdList":%s}`, array)
return []byte(jsonResult), nil
}
// json解码
func (p *Param) UnmarshalJSON(data []byte) error {
validID := regexp.MustCompile(`\{"inputIdList":\[(.*)\]\}`)
decoderData := validID.FindAllStringSubmatch(string(data), -1)
if len(decoderData) < 1 {
return errors.New("json decoder error")
}
inputIdList := strings.Split(decoderData[0][1], ",")
for _, v := range inputIdList {
id, _ := strconv.Atoi(v)
p.InputIdList = append(p.InputIdList, uint8(id))
}
return nil
}
再回到刚才配置那里,我将官方标准库注释,让他使用回go-json库,发现现在解码和编码都按预期输出了
总结
- 框架迁移时,尽量使用之前的编解码库,比如json库,虽然说go-json是和encoding/json是兼容的,但是可能部分场景下解析不同,如果你之前业务已经上线了,那切换时测试覆盖率能否达到100%?如果不能,请不要轻易切换
- json解析某个结构时,可以自定义编解码方法