gRPC如果值为false,则不返回布尔值。

huangapple go评论84阅读模式
英文:

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 the JSON marshaler from this package "google.golang.org/protobuf/encoding/protojson":
protojson.Marshaler{EmitDefaults: true}.Marshal(w, resp)

as outlined in this answer.

huangapple
  • 本文由 发表于 2023年2月5日 20:59:35
  • 转载请务必保留本文链接:https://go.coder-hub.com/75352422.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定