英文:
gRPC not returning boolean if value is false
问题
func (m *TodoServer) GetTodos(ctx context.Context, empty *emptypb.Empty) (*desc.GetTodosResponse, error) {
todos, err := m.todoService.GetTodos()
if err != nil {
return nil, err
}
todosResp := make([]*desc.GetTodosResponse_Todo, 0, len(todos))
for _, todo := range todos {
todosResp = append(todosResp, &desc.GetTodosResponse_Todo{
Id: todo.ID,
Title: todo.Title,
IsCompleted: todo.IsCompleted,
})
}
return &desc.GetTodosResponse{Todos: todosResp}, nil
}
service TodoService {
rpc GetTodos(google.protobuf.Empty) returns (GetTodosResponse) {}
}
message GetTodosResponse {
repeated Todo todos = 1;
message Todo {
int64 id = 1;
string title = 2;
bool is_completed = 3;
}
}
数据库中有一条记录:
id | title | is_completed |
---|---|---|
1 | aaa | false |
上述函数返回的结果是 { "todos": [{ "id": "1", "title": "aaa" }] }
,但是一旦将 is_completed
改为 true
,结果就正确了 { "todos": [{ "id": "1", "title": "aaa", "isCompleted": true }] }
。
英文:
func (m *TodoServer) GetTodos(ctx context.Context, empty *emptypb.Empty) (*desc.GetTodosResponse, error) {
todos, err := m.todoService.GetTodos()
if err != nil {
return nil, err
}
todosResp := make([]*desc.GetTodosResponse_Todo, 0, len(todos))
for _, todo := range todos {
todosResp = append(todosResp, &desc.GetTodosResponse_Todo{
Id: todo.ID,
Title: todo.Title,
IsCompleted: todo.IsCompleted,
})
}
return &desc.GetTodosResponse{Todos: todosResp}, nil
}
service TodoService {
rpc GetTodos(google.protobuf.Empty) returns (GetTodosResponse) {}
}
message GetTodosResponse {
repeated Todo todos = 1;
message Todo {
int64 id = 1;
string title = 2;
bool is_completed = 3;
}
}
I have one record in the db
id | title | is_completed |
---|---|---|
1 | aaa | false |
the function above returns {"todos": [{"id": "1", "title": "aaa"}]}
but once I change the is_completed
to true
the result is correct {"todos": [{"id": "1", "title": "aaa", "isCompleted": true}]}
答案1
得分: 4
这是出于设计和效率考虑。
对于bool
类型,其“零”值是false
。因此,当使用标准库的encoding/json
解码器初始化你的protobuf
结构体时,如果字段的初始值为false
,则不需要显式地指定该字段。在编码端,如果字段的JSON标签包含omitempty
限定符,标准库的encoding/json
编码器将删除任何零值,这就是你所看到的情况。
如果Title
字符串字段的初始值为""
(即字符串的零值),你将看到相同的行为。
从你生成的代码(*.pb.go
)来看,结构体的bool
字段定义大致如下:
type Todo struct {
// ...
IsCompleted bool `protobuf:"varint,5,opt,name=is_complete,proto3" json:"is_complete,omitempty"`
}
因此,json:"...,omitempty"
指示encoding/json
编码器在编组时使用这些标签时省略任何零值。
如果你想覆盖这种行为:
- 你可以从生成的代码中删除
omitempty
指令(不建议这样做,因为需要在开发的整个生命周期中管理编辑)。但如果你必须这样做,请参考这个答案; - 如果使用
grpc-gateway
,可以在运行时覆盖这个行为,例如:
gwmux := runtime.NewServeMux(runtime.WithMarshalerOption(runtime.MIMEWildcard, &runtime.JSONPb{OrigName: true, EmitDefaults: true}))
- 或者,如果自己导出JSON,而不使用标准库的
encoding/json
,可以使用google.golang.org/protobuf/encoding/protojson
包中的JSON
编码器,如下所示:
protojson.Marshaler{EmitDefaults: true}.Marshal(w, resp)
具体请参考这个答案。
英文:
This is by design and for efficiency.
The "zero" value for a bool
is false
- so when initializing your protobuf
struct with a false
value, the field does not need to be explicitly stated when using the standard libary's encoding/json
unmarshaler. On the encoding end, if the field's JSON tag includes an omitempty
qualifier, the standard libary's encoding/json
marshaler will remove any zero values - which is what you are seeing.
You will see the same behavior with the Title
string field if it was ""
(i.e. the zero value of a string).
Looking at your generated code (*.pb.go
) the struct's bool
field definition will look something like this:
type Todo struct {
// ...
IsCompleted bool `protobuf:"varint,5,opt,name=is_complete,proto3" json:"is_complete,omitempty"`
}
So the json:"...,omitempty"
instructs the encoding/json
marshaler to omit any zero values during marshalling with tags like theses.
If you want to override this behavior:
- one could remove the
omitempty
directive from your generated code (not recommended - as edits would need to be managed over the lifecycle of development). But if you must, refer to this answer; - if using
grpc-gateway
, override this at runtime e.g.
gwmux := runtime.NewServeMux(runtime.WithMarshalerOption(runtime.MIMEWildcard, &runtime.JSONPb{OrigName: true, EmitDefaults: true}))
- or if exporting the JSON yourself, instead of using the standard library (
encoding/json
) use theJSON
marshaler from this package"google.golang.org/protobuf/encoding/protojson"
:
protojson.Marshaler{EmitDefaults: true}.Marshal(w, resp)
as outlined in this answer.
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论