zeromicro / go-zero

A cloud-native Go microservices framework with cli tool for productivity.
https://go-zero.dev
MIT License
29.11k stars 3.93k forks source link

If empty slice returns json, it will become null. #1942

Closed decenfrontier closed 2 years ago

decenfrontier commented 2 years ago

Describe the bug The code here is the API side, you can see, If empty slice returns json, it will become null. image

you can see, the pbPullRepsonse.List is also an empty slice, and after copier.Copy, it still empty slice, but when it is serialized to json, the empty slice becomes null image

To Reproduce PS: If you want to see the complete code, it is open source at https://github.com/CPALyth/ws_chat

  1. The code is
// api
func (l *PullLogic) Pull(req *types.PullRequest) (*types.PullResponse, error) {
    uid := ctxdata.GetUidFromCtx(l.ctx)
    var pbPullRequest proto.PullRequest
    copier.Copy(&pbPullRequest, req)
    pbPullRequest.UserId = uid
    pbPullResponse, err := l.svcCtx.MessageRpc.Pull(l.ctx, &pbPullRequest)
    if err != nil {
        return nil, err
    }
    var resp types.PullResponse
    copier.Copy(&resp, pbPullResponse)
    fmt.Printf("%+v\n", resp.List)

    // 下面的代码是为了实现 空切片返回的json是[], 而不是null
    // if len(resp.List) == 0 {
    //  resp.List = make([]*types.ChatMsg, 0)
    // }
    return &resp, nil
}
   // rpc
func (l *PullLogic) Pull(in *proto.PullRequest) (*proto.PullResponse, error) {
    userId := in.UserId
    groupId := in.GroupId
    platform := in.Platform
    maxMsgId := in.MaxMsgId  // 最新的msgId
    // 上次收到在线消息的位置, 没有则为0
    var lastMsgId int64
    // 先查询缓存的last_msg_id
    cache_key := fmt.Sprintf("%d:%s:%s", userId, platform, groupId)
    cache_val, err := l.svcCtx.RedisClient.Get(cache_key)
    if err == nil {  // 若查到上次收到在线消息的位置
        lastMsgId, err = strconv.ParseInt(cache_val, 10, 64)
        if err != nil {
            return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_ERROR),
                "pull cache error, val:%s, err:%v", cache_val, err)
        }
        fmt.Println("last_msg_id:", lastMsgId)
        if lastMsgId >= maxMsgId {
            // 上次收到在线消息的位置 已经是 最新位置, 则不需要拉取
            return nil, nil
        }
    }
    // 查询数据库, 从(maxMsgId, last_msg_id)倒序返回10条数据
    list, err := l.svcCtx.ChatMsgModel.FindMsgListByLastMsgId(l.ctx, groupId, lastMsgId, maxMsgId)
    if err != nil && err != model.ErrNotFound {
        return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR),
            "message pull error, uid:%s,err:%v", userId, err)
    }
    // 组装返回值
    var resp []*proto.ChatMsg
    if len(list) > 0 {
        for _, chatMsg := range list {
            var pbChatMsg proto.ChatMsg
            _ = copier.Copy(&pbChatMsg, chatMsg)
            pbChatMsg.CreateTime = chatMsg.CreateTime.UnixMilli()
            resp = append(resp, &pbChatMsg)
        }
    }
    return &proto.PullResponse{
        List: resp,
    }, nil
}

Expected behavior If empty slice returns json, it will become [].

Environments

decenfrontier commented 2 years ago

I found that empty slices became null after httpx called json.Marshal.

kevwan commented 2 years ago

Give a simple enough and runnable code to reproduce it?

decenfrontier commented 2 years ago

This does not seem to be the bug of gozero. Json.Marshal automatically changes slices of unallocated memory into null.

Please forgive me for being a beginner in go and I don't quite understand the details of the go language.

package main

import (
    "encoding/json"
    "fmt"
)

type Body struct {
    Code uint32      `json:"code"`
    Msg  string      `json:"msg"`
    Data interface{} `json:"data"`
}

func main() {
    var data []string
    fmt.Println(data == nil)  // true
    var body = Body{
        Code: 0,
        Msg: "获取消息成功",
        Data: data,
    }
    bytes, err := json.Marshal(body)
    if err != nil {
        panic(err)
    }
    print(string(bytes))  // {"code":0,"msg":"获取消息成功","data":null}
}