Search code examples
gogrpc

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;
    }
}

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}]}


Solution

  • 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.