本文最后更新于:2023年7月26日 晚上

前文

由于项目组用了一个轻量的golang rpc框架——drpc,出现了http接口,json字段omitempty无法配置,导致restful接口,字段值是对应类型的零值时,接口不是返回零值,而是直接不返回该字段。如接口预期返回是:

{
  "code": 0,
  "message": "hello world",
  "data": []
}

实际drpc的restful服务返回的是:

{
  "message": "hello world"
}

因此研究了下drpc的源码,并进行了相应改造,解决这一问题

定位

通过观察pb文件,发现drpc使用的json marshal工具不是内置的encording/json 包,而是 protojson 包
image
再看protojson 的MarshalOptions源码,就很清晰了,EmitUnpopulated 就是类似json tag的omitempty

// MarshalOptions is a configurable JSON format marshaler.
type MarshalOptions struct {
	pragma.NoUnkeyedLiterals

	// Multiline specifies whether the marshaler should format the output in
	// indented-form with every textual element on a new line.
	// If Indent is an empty string, then an arbitrary indent is chosen.
	Multiline bool

	// Indent specifies the set of indentation characters to use in a multiline
	// formatted output such that every entry is preceded by Indent and
	// terminated by a newline. If non-empty, then Multiline is treated as true.
	// Indent can only be composed of space or tab characters.
	Indent string

	// AllowPartial allows messages that have missing required fields to marshal
	// without returning an error. If AllowPartial is false (the default),
	// Marshal will return error if there are any missing required fields.
	AllowPartial bool

	// UseProtoNames uses proto field name instead of lowerCamelCase name in JSON
	// field names.
	UseProtoNames bool

	// UseEnumNumbers emits enum values as numbers.
	UseEnumNumbers bool

	// EmitUnpopulated specifies whether to emit unpopulated fields. It does not
	// emit unpopulated oneof fields or unpopulated extension fields.
	// The JSON value emitted for unpopulated fields are as follows:
	//  ╔═══════╤════════════════════════════╗
	//  ║ JSON  │ Protobuf field             ║
	//  ╠═══════╪════════════════════════════╣
	//  ║ false │ proto3 boolean fields      ║
	//  ║ 0     │ proto3 numeric fields      ║
	//  ║ ""    │ proto3 string/bytes fields ║
	//  ║ null  │ proto2 scalar fields       ║
	//  ║ null  │ message fields             ║
	//  ║ []    │ list fields                ║
	//  ║ {}    │ map fields                 ║
	//  ╚═══════╧════════════════════════════╝
	EmitUnpopulated bool

	// Resolver is used for looking up types when expanding google.protobuf.Any
	// messages. If nil, this defaults to using protoregistry.GlobalTypes.
	Resolver interface {
		protoregistry.ExtensionTypeResolver
		protoregistry.MessageTypeResolver
	}
}

接下来的工作就是让drpc生成的pb代码中,protojson.Marshal 方法使用这个配置。所以再回到drpc源码,找到生成这一段代码的位置:
image

并做如下修改:
image

剩下的工作就是把protoc-gen-go-drpc工具重新编译即可,最终生成的pb文件,protocjson.Marshal方法也成功使用了MarshalOptions配置:
image

附上生成drpc pb代码的镜像

FROM golang:1.19-alpine3.16 as builder
ENV GOPROXY=https://goproxy.cn,direct

WORKDIR /src
COPY . /src

RUN go mod download -x
RUN go build -o /bin/protoc-gen-go cmd/protoc-gen-go/main.go
RUN go build -o /bin/protoc-gen-go-drpc main.go

FROM alpine:3.16
RUN apk update
RUN apk add protobuf
COPY --from=builder /bin/protoc-gen-go-drpc /bin
COPY --from=builder /bin/protoc-gen-go /bin

WORKDIR /protobuf

ENTRYPOINT protoc --go_out=/out --go-drpc_out=/out --proto_path=. $(find ./ -name '*.proto')

# docker build -f Dockerfile -t tc/protoc-drpc .
# docker run --rm -v $(pwd)/protobuf:/protobuf -v $(pwd):/out tc/protoc-drpc

PS

debug期间也对protobuf-go工具,做了修改,实现生成的pb文件的字段,json tag 不携带 omitempty,具体修改如下:
image

image
最后,非必要还是用grpc这种社区更活跃的框架吧


本博客所有文章除特别声明外,均采用 CC BY-SA 3.0协议 。转载请注明出处!

go net/http的小总结 上一篇
记一次go base64编码排查 下一篇