Golang GIN写单测时,愉快的使用返回值

我写的接口,基本长这样:

{
    "code": 200,
    "msg": "原因",
    "result": {} // 或者空或者其它
}

所以在Go里,定义如下:

type Resp struct {
	Code   int         `json:"code"`
	Msg    string      `json:"msg"`
	Result interface{} `json:"result"`
}

然后,在返回值时,就可以定义好结构,比如:

type UserResult struct {
	UserID int `json:"user_id"`
}

然后把值塞进去。这样就愉快的返回了结果,不过,单测的时候可咋办呢?因为我想判断 result 里的值。最开始我尝试了如下方案:

func bodyToStruct(byteArray []byte, s interface{}) {
	err := json.Unmarshal(byteArray, &s)
	if err != nil {
		log.Printf("failed to unmarshal %s: %s", byteArray, err)
	}
}

这是一个把 []byte 转换成对应结构体的帮助函数,不过,result是不好转的,一开始我有尝试这样:

resp := Resp{Result: UserResult{}}

但是没有用,拿出来以后,Result 的值是一个map,于是我采用了曲线救国的方式,加一个工具函数,先把 Result 的值 marshal, 然后unmarshal到对应的结构体:

func resultToStruct(result, s interface{}) {
    byteArray, err := json.Marshal(result)
    if err != nil {
        logrus.Errorf("failed to marshal %s: %s", result, err)
        return
    }
 
    err = json.Unmarshal(byteArray, s)
    if err != nil {
        logrus.Errorf("failed to unmarshal %s: %s", byteArray, err)
        return
    }
}

很明显,有点low,转来转去。经过群友的提醒,是我最上面应该传指针,于是改成这样就可以了:

resp := Resp{Result: &Result}

这样就可以愉快的把值一次性通过 bodyToStruct 放到对应的结构体里,但是,要怎么样才能愉快的取值呢?因为即便做到了刚才那样, 如果你直接 resp.Result.UserID 还是不行,因为 Resp.Result 的定义是一个 interface{},解决方案如下:

resp := Resp{}
result := Result{}
resp.Result = &result

bodyToStruct(bytes, &resp)

于是就可以了。看看demo代码:

package main

import (
	"encoding/json"
	"log"
)

type Resp struct {
	Code   int         `json:"code"`
	Msg    string      `json:"msg"`
	Result interface{} `json:"result"`
}

type UserResult struct {
	UserID int `json:"user_id"`
}

func bodyToStruct(byteArray []byte, s interface{}) {
	err := json.Unmarshal(byteArray, &s)
	if err != nil {
		log.Printf("failed to unmarshal %s: %s", byteArray, err)
	}
}

func main() {
	byteArray := []byte(`{"code":200,"msg":"","result":{"user_id": 1}}`)

	resp := Resp{}
	result := UserResult{}
	resp.Result = &result

	bodyToStruct(byteArray, &resp)
	log.Printf("user id is %d", result.UserID)
}

于是,就终于可以愉快的类型安全的使用返回值了。这样做有几个好处:

  • 类型安全
  • 可以复用定义接口时写的struct
  • 可以补全

有同学可能认为,为啥不直接用interface,断言?嗯,其实我曾经年少轻狂的时候,经常说我是interface走天下,直到我的程序不断的 崩崩崩,我才老老实实重新做人,老老实实的类型安全。

这就是这次想要分享的技巧。


微信公众号
关注公众号,获得及时更新

更多文章
  • Web开发系列(六):关系型数据库,ORM
  • Web开发系列(七):缓存,CDN
  • Web开发系列(八):单点故障,负载均衡
  • Web开发系列(五):form, json, xml
  • Web开发系列(四):Flask, Tornado和WSGI
  • Web开发系列(三):什么是HTML,CSS,JS?
  • Web开发系列(二):HTTP协议
  • Web开发系列(一):从输入网址到最后,这个过程经历了什么?
  • SNI: 让Nginx在一个IP上使用多个证书
  • Haskell: infixl, infixr, infix
  • Haskell简明教程(五):处理JSON
  • Haskell简明教程(四):Monoid, Applicative, Monad
  • HTTPS 的详细流程
  • OAuth2 为什么需要 Authorization Code?
  • 任务队列怎么写?python rq源码阅读与分析