最近,在使用grpc,grpc-gateway时,遇到一个奇怪的问题,返回结果中一个参数在proto中定义的类型是int64,但通过grpc-gateway返回的json结果数据中确变成了string类型,为啥返回的类型和proto定义的类型不一样呢?难道是grpc-gateway做了手脚,于是一通狂搜和翻阅源码,终于解决了心中的疑惑。
问题复现
定义的proto文件
message Response {
int64 total = 1;
......
}
grpc-gateway返回的json数据
{
"total": "1000",
......
}
我们看到total字段在proto中的定义是int64类型,但返回的json数据类型是string,通过查proto的文档,发现官方已经定义了某些类型以及对应的返回值,其中int64类型对应的json序列化的类型是string,如下图:
查看grpc的代码,找到实现如下:
// marshalSingular marshals the given non-repeated field value. This includes
// all scalar types, enums, messages, and groups.
func (e encoder) marshalSingular(val protoreflect.Value, fd protoreflect.FieldDescriptor) error {
if !val.IsValid() {
e.WriteNull()
return nil
}
switch kind := fd.Kind(); kind {
case protoreflect.BoolKind:
e.WriteBool(val.Bool())
case protoreflect.StringKind:
if e.WriteString(val.String()) != nil {
return errors.InvalidUTF8(string(fd.FullName()))
}
case protoreflect.Int32Kind, protoreflect.Sint32Kind, protoreflect.Sfixed32Kind:
e.WriteInt(val.Int())
case protoreflect.Uint32Kind, protoreflect.Fixed32Kind:
e.WriteUint(val.Uint())
case protoreflect.Int64Kind, protoreflect.Sint64Kind, protoreflect.Uint64Kind,
protoreflect.Sfixed64Kind, protoreflect.Fixed64Kind:
// 64-bit integers are written out as JSON string.
e.WriteString(val.String())
default:
panic(fmt.Sprintf("%v has unknown kind: %v", fd.FullName(), kind))
}
return nil
}
看上边这段代码发现,grpc中在序列化protobuf时,将int64、unsigned int64类型序列化时转换成string, 而int32,unsigned int32还是序列化成原有类型。搜索发现github issus上有一个相关解释,说和javascript中number的实现有关,javascript中的number的最大精度是2的52次方,如果超过该数, 就会缺少精度。因此,grpc的处理方式是将int64处理成string类型。
解决方案
知道了原因,解决方案就很简单了,将proto中定义的int64类型改为int32就可以了。