Golang How to define gRPC message to contain mixed data type in an array

eye-catch Golang

Check the following post first if you have not prepared a dev-env for gRPC in Go lang.

In this post, we will go through the possible keywords that we can use for special data types. It’s better to know which data type and keywords can be used in advance.

You can clone my GitHub repository and try it yourself.

Sponsored links

Function List

This is the function list that we will try in this post.

syntax = "proto3";

option go_package = "pkg/api-test/grpc/apitest";

service TypesDef {
  // rpc VariousDef(VariousDefRequest) returns (VariousDefResponse) {}
  rpc WithInt64(WithInt64RequestResponse) returns (WithInt64RequestResponse) {}
  rpc WithOneof(WithOneofRequest) returns (WithOneofResponse) {}
  rpc WithPrimitive(WithPrimitiveRequest) returns (WithPrimitiveResponse) {}
  rpc WithOptional(WithOptionalRequest) returns (WithOptionalResponse) {}
  rpc WithRepeatedInt64(WithRepeatedInt64Request)
      returns (WithRepeatedInt64Response) {}
  rpc WithRepeatedStringInt(WithRepeatedStringIntRequest)
      returns (WithRepeatedStringIntResponse) {}
  rpc WithMap(WithMapRequest) returns (WithMapResponse) {}
}

The request/response messages will be shown in the corresponding chapter.

Sponsored links

int64 – literal value

This is the most simple request/response definition.

message WithInt64RequestResponse {
  int64 value = 1;
}

The value must be set. It’s impossible to set nil.

// client implementation
func (m *MiddleMan) WithInt64() {
    client := rpc.NewTypesDefClient(m.conn)

    ctx := context.Background()
    res, err := client.WithInt64(ctx, &rpc.WithInt64RequestResponse{Value: int64(95)})
    if err != nil {
        fmt.Printf("[ERROR in WithInt64]: %v\n", err)
        return
    }
    fmt.Printf("WithInt64: %d\n", res.Value)
}

// server implementation
func (s GrpcTypeDefHandler) WithInt64(ctx context.Context, req *rpc.WithInt64RequestResponse) (*rpc.WithInt64RequestResponse, error) {
    fmt.Printf("int64: %v\n", req.GetValue())
    return &rpc.WithInt64RequestResponse{Value: int64(1)}, nil
}

Beware that there are Value property and GetValue getter method. It’s better to stick using GetXxxx() when accessing the value because it’s safer. Value can’t be nil in this example since the value is a mandatory parameter. However, it could cause Access Violation when accessing Value directly if it’s defined as optional.

$ make runServer                        | $ make runClient
2023/06/22 14:17:58 start gRPC server   | WithInt64: 1
int64: 95                               |

oneof – What value is assigned to oneof value

oneof keyword can be used when a single property could have a different data type depending on the situation. This is a simple example definition.

message WithOneofRequest {
  oneof one_of_value {
    int64 number = 1;
    string text = 2;
  }
  string type = 3;
}
message WithOneofResponse {
  oneof one_of_value {
    int64 number = 1;
    string text = 2;
  }
}

This is the code.

// client implementation
func (m *MiddleMan) WithOneof() {
    client := rpc.NewTypesDefClient(m.conn)

    ctx := context.Background()

    req1 := &rpc.WithOneofRequest{OneOfValue: &rpc.WithOneofRequest_Number{Number: int64(23)}, Type: "int64"}
    res1, _ := client.WithOneof(ctx, req1)
    fmt.Printf("WithOneof1: %v\n", res1.GetOneOfValue())
    fmt.Printf("WithOneof1: %v\n", res1.GetNumber())
    fmt.Printf("WithOneof1: %v\n", res1.GetText())

    req2 := &rpc.WithOneofRequest{OneOfValue: &rpc.WithOneofRequest_Text{Text: "from client"}, Type: "string"}
    res2, _ := client.WithOneof(ctx, req2)
    fmt.Printf("WithOneof2: %v\n", res2.GetOneOfValue())
    fmt.Printf("WithOneof2: %v\n", res2.GetNumber())
    fmt.Printf("WithOneof2: %v\n", res2.GetText())

    req3 := &rpc.WithOneofRequest{Type: "nil"}
    res3, _ := client.WithOneof(ctx, req3)
    fmt.Printf("WithOneof3: %v\n", res3.GetOneOfValue())
    fmt.Printf("WithOneof3: %v\n", res3.GetNumber())
    fmt.Printf("WithOneof3: %v\n", res3.GetText())
}

// server implementation
func (s GrpcTypeDefHandler) WithOneof(ctx context.Context, req *rpc.WithOneofRequest) (*rpc.WithOneofResponse, error) {
    fmt.Println("---------------")
    fmt.Printf("oneof is nil: %v\n", req.OneOfValue == nil)
    fmt.Printf("oneof: %v\n", req.GetOneOfValue())
    fmt.Printf("oneof number: %v\n", req.GetNumber())
    fmt.Printf("oneof text: %v\n", req.GetText())

    switch req.GetType() {
    case "int64":
        return &rpc.WithOneofResponse{OneOfValue: &rpc.WithOneofResponse_Number{Number: int64(1)}}, nil
    case "string":
        return &rpc.WithOneofResponse{OneOfValue: &rpc.WithOneofResponse_Text{Text: "This is string."}}, nil
    case "nil":
        return &rpc.WithOneofResponse{}, nil
    default:
        return nil, status.Errorf(codes.InvalidArgument, fmt.Sprintf("not supported case '%s'", req.GetType()))
    }
}

A client sends requests with the desired type and checks the returned value. A server simply returns the value of the requested data type.

The result is as follows.

$ make runServer                         | $ make runClient   
2023/06/22 13:42:46 start gRPC server    | WithOneof1: &{1}                          
---------------                          | WithOneof1: 1  
oneof is nil: false                      | WithOneof1:       
oneof: &{23}                             | WithOneof2: &{This is string.}  
oneof number: 23                         | WithOneof2: 0      
oneof text:                              | WithOneof2: This is string.  
---------------                          | WithOneof3: <nil>  
oneof is nil: false                      | WithOneof3: 0      
oneof: &{from client}                    | WithOneof3:           
oneof number: 0                          |   
oneof text: from client                  |           
---------------                          |       
oneof is nil: true                       |       
oneof: <nil>                             |   
oneof number: 0                          |   
oneof text:                              |   

OneOfValue is a non-nil value if int value or string is assigned to it. Zero value is set to the non-assigned property. If it’s int, it’s 0. If it’s a string, it’s an empty string. As we can see in the result, nil can also be assigned to oneof property. Therefore, it should be validated before using the value.

oneof can be used for the same purpose as optional keyword that will be shown in a later chapter. If you can’t update the protocol buffer version for some reason, you can use oneof instead of optional.

Define Primitive Type by using oneof keyword

Let’s define a primitive type that can be used in multiple message definitions.

message WithPrimitiveRequest {
  PrimitiveType primitive = 1;
  string type = 2;
}

message WithPrimitiveResponse {
  PrimitiveType primitive = 1;
}

message PrimitiveType {
  message Value {
    oneof element {
      string text = 1;
      double double_value = 2;
      int64 int64_value = 3;
      uint64 uint64_value = 4;
      bool boolean = 5;
      bytes raw_bytes = 6;
    }
  }
  Value value = 1;
}

The implementation is long but it does simple things. It sets value/type and send it.

// client implementation
func (m *MiddleMan) WithPrimitive() {
    client := rpc.NewTypesDefClient(m.conn)

    ctx := context.Background()

    req1 := &rpc.WithPrimitiveRequest{
        Type: "string",
        Primitive: &rpc.PrimitiveType{Value: &rpc.PrimitiveType_Value{
            Element: &rpc.PrimitiveType_Value_Text{Text: "This is string."},
        }},
    }
    res1, _ := client.WithPrimitive(ctx, req1)
    fmt.Printf("WithPrimitive1: %v\n", res1.GetPrimitive().GetValue().GetElement())
    fmt.Printf("WithPrimitive1: %v\n", res1.GetPrimitive().GetValue().GetText())
    fmt.Printf("WithPrimitive1: %v\n", res1.GetPrimitive().GetValue().GetInt64Value())

    req2 := &rpc.WithPrimitiveRequest{
        Type: "double",
        Primitive: &rpc.PrimitiveType{Value: &rpc.PrimitiveType_Value{
            Element: &rpc.PrimitiveType_Value_DoubleValue{DoubleValue: 5.5},
        }},
    }
    res2, _ := client.WithPrimitive(ctx, req2)
    fmt.Printf("WithPrimitive2: %v\n", res2.GetPrimitive().GetValue().GetDoubleValue())

    req3 := &rpc.WithPrimitiveRequest{
        Type: "int64",
        Primitive: &rpc.PrimitiveType{Value: &rpc.PrimitiveType_Value{
            Element: &rpc.PrimitiveType_Value_Int64Value{Int64Value: int64(99)},
        }},
    }
    res3, _ := client.WithPrimitive(ctx, req3)
    fmt.Printf("WithPrimitive3: %v\n", res3.GetPrimitive().GetValue().GetInt64Value())

    req4 := &rpc.WithPrimitiveRequest{
        Type: "uint64",
        Primitive: &rpc.PrimitiveType{Value: &rpc.PrimitiveType_Value{
            Element: &rpc.PrimitiveType_Value_Uint64Value{Uint64Value: uint64(123)},
        }},
    }
    res4, _ := client.WithPrimitive(ctx, req4)
    fmt.Printf("WithPrimitive4: %v\n", res4.GetPrimitive().GetValue().GetUint64Value())

    req5 := &rpc.WithPrimitiveRequest{
        Type: "boolean",
        Primitive: &rpc.PrimitiveType{Value: &rpc.PrimitiveType_Value{
            Element: &rpc.PrimitiveType_Value_Boolean{Boolean: true},
        }},
    }
    res5, _ := client.WithPrimitive(ctx, req5)
    fmt.Printf("WithPrimitive5: %v\n", res5.GetPrimitive().GetValue().GetBoolean())

    req6 := &rpc.WithPrimitiveRequest{
        Type: "raw_bytes",
        Primitive: &rpc.PrimitiveType{Value: &rpc.PrimitiveType_Value{
            Element: &rpc.PrimitiveType_Value_RawBytes{RawBytes: []byte{40, 41, 42}},
        }},
    }
    res6, _ := client.WithPrimitive(ctx, req6)
    fmt.Printf("WithPrimitive6: %v\n", res6.GetPrimitive().GetValue().GetRawBytes())

    req7 := &rpc.WithPrimitiveRequest{Type: "nil1"}
    res7, _ := client.WithPrimitive(ctx, req7)
    fmt.Printf("WithPrimitive7: %v\n", res7.GetPrimitive().GetValue())
}

// server implementation
func (s GrpcTypeDefHandler) WithPrimitive(ctx context.Context, req *rpc.WithPrimitiveRequest) (*rpc.WithPrimitiveResponse, error) {
    fmt.Println("---------------")
    fmt.Printf("Primitive is nil: %v\n", req.Primitive == nil)
    fmt.Printf("Primitive value: %v\n", req.GetPrimitive().GetValue())
    fmt.Printf("Primitive Text: %v\n", req.GetPrimitive().GetValue().GetText())
    fmt.Printf("Primitive DoubleValue: %v\n", req.GetPrimitive().GetValue().GetDoubleValue())
    fmt.Printf("Primitive Int64Value: %v\n", req.GetPrimitive().GetValue().GetInt64Value())
    fmt.Printf("Primitive Uint64Value: %v\n", req.GetPrimitive().GetValue().GetUint64Value())
    fmt.Printf("Primitive Boolean: %v\n", req.GetPrimitive().GetValue().GetBoolean())
    fmt.Printf("Primitive RawBytes: %v\n", req.GetPrimitive().GetValue().GetRawBytes())

    switch req.GetType() {
    case "string":
        return &rpc.WithPrimitiveResponse{Primitive: &rpc.PrimitiveType{Value: &rpc.PrimitiveType_Value{Element: &rpc.PrimitiveType_Value_Text{Text: "This is string."}}}}, nil
    case "double":
        return &rpc.WithPrimitiveResponse{Primitive: &rpc.PrimitiveType{Value: &rpc.PrimitiveType_Value{Element: &rpc.PrimitiveType_Value_DoubleValue{DoubleValue: 5.5}}}}, nil
    case "int64":
        return &rpc.WithPrimitiveResponse{Primitive: &rpc.PrimitiveType{Value: &rpc.PrimitiveType_Value{Element: &rpc.PrimitiveType_Value_Int64Value{Int64Value: int64(99)}}}}, nil
    case "uint64":
        return &rpc.WithPrimitiveResponse{Primitive: &rpc.PrimitiveType{Value: &rpc.PrimitiveType_Value{Element: &rpc.PrimitiveType_Value_Uint64Value{Uint64Value: uint64(123)}}}}, nil
    case "boolean":
        return &rpc.WithPrimitiveResponse{Primitive: &rpc.PrimitiveType{Value: &rpc.PrimitiveType_Value{Element: &rpc.PrimitiveType_Value_Boolean{Boolean: true}}}}, nil
    case "raw_bytes":
        return &rpc.WithPrimitiveResponse{Primitive: &rpc.PrimitiveType{Value: &rpc.PrimitiveType_Value{Element: &rpc.PrimitiveType_Value_RawBytes{RawBytes: []byte{40, 41, 42}}}}}, nil
    case "nil1":
        return &rpc.WithPrimitiveResponse{}, nil
    case "nil2":
        return &rpc.WithPrimitiveResponse{Primitive: &rpc.PrimitiveType{}}, nil
    case "nil3":
        return &rpc.WithPrimitiveResponse{Primitive: &rpc.PrimitiveType{Value: &rpc.PrimitiveType_Value{}}}, nil
    default:
        return nil, status.Errorf(codes.InvalidArgument, fmt.Sprintf("not supported case '%s'", req.GetType()))
    }
}

The result is as follows.

$ make runServer                        | $ make runClient
2023/06/22 14:33:20 start gRPC server   |
---------------                         |
Primitive is nil: false                 | WithPrimitive1: &{This is string.}
Primitive value: text:"This is string." | WithPrimitive1: This is string. 
Primitive Text: This is string.         | WithPrimitive1: 0
Primitive DoubleValue: 0                |
Primitive Int64Value: 0                 |
Primitive Uint64Value: 0                |
Primitive Boolean: false                |
Primitive RawBytes: []                  |
---------------                         |
Primitive is nil: false                 | WithPrimitive2: 5.5
Primitive value: double_value:5.5       |
Primitive Text:                         |
Primitive DoubleValue: 5.5              |
Primitive Int64Value: 0                 |
Primitive Uint64Value: 0                |
Primitive Boolean: false                |
Primitive RawBytes: []                  |
---------------                         |
Primitive is nil: false                 | WithPrimitive3: 99
Primitive value: int64_value:99         |
Primitive Text:                         |
Primitive DoubleValue: 0                |
Primitive Int64Value: 99                |
Primitive Uint64Value: 0                |
Primitive Boolean: false                |
Primitive RawBytes: []                  |
---------------                         |
Primitive is nil: false                 | WithPrimitive4: 123
Primitive value: uint64_value:123       |
Primitive Text:                         |
Primitive DoubleValue: 0                |
Primitive Int64Value: 0                 |
Primitive Uint64Value: 123              |
Primitive Boolean: false                |
Primitive RawBytes: []                  |
---------------                         |
Primitive is nil: false                 | WithPrimitive5: true
Primitive value: boolean:true           |
Primitive Text:                         |
Primitive DoubleValue: 0                |
Primitive Int64Value: 0                 |
Primitive Uint64Value: 0                |
Primitive Boolean: true                 |
Primitive RawBytes: []                  |
---------------                         |
Primitive is nil: false                 | WithPrimitive6: [40 41 42]
Primitive value: raw_bytes:"()*"        |
Primitive Text:                         |
Primitive DoubleValue: 0                |
Primitive Int64Value: 0                 |
Primitive Uint64Value: 0                |
Primitive Boolean: false                |
Primitive RawBytes: [40 41 42]          |
---------------                         |
Primitive is nil: true                  | WithPrimitive7: <nil>
Primitive value: <nil>                  |
Primitive Text:                         |
Primitive DoubleValue: 0                |
Primitive Int64Value: 0                 |
Primitive Uint64Value: 0                |
Primitive Boolean: false                |
Primitive RawBytes: []                  |

If this primitive type needs to be used in the production code, we should prepare a utility to get one of the values by checking whether the value is nil or not.

optional

optional can be used if the value is not a mandatory parameter. In this case, the property will be defined as a pointer and set to nil if no value is provided.

message WithOptionalRequest {
  optional int64 option_value = 1;
  string type = 2;
}
message WithOptionalResponse {
  optional int64 option_value = 1;
}

Note that nil can be explicitly set to the property but it’s impossible to differentiate the following two cases.

  • nil is set to the property
  • the property is not provided
// client implementation
func (m *MiddleMan) WithOptional() {
    client := rpc.NewTypesDefClient(m.conn)

    ctx := context.Background()

    req1 := &rpc.WithOptionalRequest{OptionValue: nil, Type: "nil"}
    res1, _ := client.WithOptional(ctx, req1)
    fmt.Printf("WithOptional1 is nil: %v\n", res1.OptionValue == nil)
    fmt.Printf("WithOptional1: %v\n", res1.GetOptionValue())

    val := int64(866)
    req2 := &rpc.WithOptionalRequest{OptionValue: &val, Type: ""}
    res2, _ := client.WithOptional(ctx, req2)
    fmt.Printf("WithOptional2 is nil: %v\n", res2.OptionValue == nil)
    fmt.Printf("WithOptional2: %v\n", res2.GetOptionValue())
}

// server implementation
func (s GrpcTypeDefHandler) WithOptional(ctx context.Context, req *rpc.WithOptionalRequest) (*rpc.WithOptionalResponse, error) {
    fmt.Println("---------------")
    fmt.Printf("Optional is nil: %v\n", req.OptionValue == nil)
    fmt.Printf("Optional: %v\n", req.GetOptionValue())

    switch req.GetType() {
    case "nil":
        return &rpc.WithOptionalResponse{}, nil
    default:
        val := int64(64)
        return &rpc.WithOptionalResponse{OptionValue: &val}, nil
    }
}

Zero value can be obtained when using GetXxxxx() if the value is nil.

$ make runServer                        | $ make runClient
2023/06/22 14:49:01 start gRPC server   | WithOptional1 is nil: true
---------------                         | WithOptional1: 0
Optional is nil: true                   | WithOptional2 is nil: false
Optional: 0                             | WithOptional2: 64
---------------                         |
Optional is nil: false                  |
Optional: 866                           |

repeated – creating an array

repeated keyword can be used if we need an array.

message WithRepeatedInt64Request {
  repeated int64 int_array = 1;
  string type = 2;
}

message WithRepeatedInt64Response {
  repeated int64 int_array = 1;
}

nil can be set to the property defined with repeated keyword.

// client implementation
func (m *MiddleMan) WithRepeatedInt64() {
    client := rpc.NewTypesDefClient(m.conn)

    ctx := context.Background()

    req1 := &rpc.WithRepeatedInt64Request{IntArray: nil, Type: "nil1"}
    res1, _ := client.WithRepeatedInt64(ctx, req1)
    fmt.Printf("WithRepeatedInt64_1 is nil: %v\n", res1.IntArray == nil)
    fmt.Printf("WithRepeatedInt64_1: %v\n", res1.GetIntArray())

    req2 := &rpc.WithRepeatedInt64Request{IntArray: []int64{}, Type: "empty"}
    res2, _ := client.WithRepeatedInt64(ctx, req2)
    fmt.Printf("WithRepeatedInt64_2 is nil: %v\n", res2.IntArray == nil)
    fmt.Printf("WithRepeatedInt64_2: %v\n", res2.GetIntArray())

    req3 := &rpc.WithRepeatedInt64Request{IntArray: []int64{1, 2}, Type: ""}
    res3, _ := client.WithRepeatedInt64(ctx, req3)
    fmt.Printf("WithRepeatedInt64_3 is nil: %v\n", res3.IntArray == nil)
    fmt.Printf("WithRepeatedInt64_3: %v\n", res3.GetIntArray())
}

// server implementation
func (s GrpcTypeDefHandler) WithRepeatedInt64(ctx context.Context, req *rpc.WithRepeatedInt64Request) (*rpc.WithRepeatedInt64Response, error) {
    fmt.Println("---------------")
    fmt.Printf("Repeated is nil: %v\n", req.GetIntArray() == nil)
    fmt.Printf("Repeated: %v\n", req.GetIntArray())

    switch req.GetType() {
    case "nil1":
        return &rpc.WithRepeatedInt64Response{}, nil
    case "nil2":
        return &rpc.WithRepeatedInt64Response{IntArray: nil}, nil
    case "empty":
        return &rpc.WithRepeatedInt64Response{IntArray: []int64{}}, nil
    default:
        return &rpc.WithRepeatedInt64Response{IntArray: []int64{int64(11), int64(22), int64(33)}}, nil
    }
}

Look at the second request. It sets an empty array but it is handled as nil. It seems that this is a limitation of protocol buffer at the moment.

$ make runServer                        | $ make runClient
2023/06/22 14:53:49 start gRPC server   | WithRepeatedInt64_1 is nil: true
---------------                         | WithRepeatedInt64_1: []
Repeated is nil: true                   | WithRepeatedInt64_2 is nil: true
Repeated: []                            | WithRepeatedInt64_2: []
---------------                         | WithRepeatedInt64_3 is nil: false
Repeated is nil: true                   | WithRepeatedInt64_3: [11 22 33]
Repeated: []                            |
---------------                         |
Repeated is nil: false                  |
Repeated: [1 2]                         |

Check the length first before accessing the array since GetXxxx() returns nil when it’s nil.

repeated – mixed data type

We might need to add int and string in an array. We can do it by using oneof keyword.

message WithRepeatedStringIntRequest {
  repeated StringIntegerValue string_int_array = 1;
  string type = 2;
}

message WithRepeatedStringIntResponse {
  repeated StringIntegerValue string_int_array = 1;
}

message StringIntegerValue {
  oneof value {
    string text = 1;
    int64 number = 2;
  }
}

We need to use the desired struct to add int/string value accordingly.

// client implementation
func (m *MiddleMan) WithRepeatedStringInt() {
    client := rpc.NewTypesDefClient(m.conn)

    ctx := context.Background()

    req1 := &rpc.WithRepeatedStringIntRequest{StringIntArray: nil, Type: "nil1"}
    res1, _ := client.WithRepeatedStringInt(ctx, req1)
    fmt.Printf("WithRepeatedStringInt_1 is nil: %v\n", res1.StringIntArray == nil)
    fmt.Printf("WithRepeatedStringInt_1: %v\n", res1.GetStringIntArray())

    req2 := &rpc.WithRepeatedStringIntRequest{StringIntArray: []*rpc.StringIntegerValue{}, Type: "empty"}
    res2, _ := client.WithRepeatedStringInt(ctx, req2)
    fmt.Printf("WithRepeatedStringInt_2 is nil: %v\n", res2.StringIntArray == nil)
    fmt.Printf("WithRepeatedStringInt_2: %v\n", res2.GetStringIntArray())

    // only int value
    req3 := &rpc.WithRepeatedStringIntRequest{
        StringIntArray: []*rpc.StringIntegerValue{
            {Value: &rpc.StringIntegerValue_Number{Number: int64(543)}},
            {Value: &rpc.StringIntegerValue_Number{Number: int64(777)}},
        },
        Type: "number",
    }
    res3, _ := client.WithRepeatedStringInt(ctx, req3)
    fmt.Printf("WithRepeatedStringInt_3 is nil: %v\n", res3.StringIntArray == nil)
    fmt.Printf("WithRepeatedStringInt_3: %v\n", res3.GetStringIntArray())

    // only string value
    req4 := &rpc.WithRepeatedStringIntRequest{
        StringIntArray: []*rpc.StringIntegerValue{
            {Value: &rpc.StringIntegerValue_Text{Text: "from client 1"}},
            {Value: &rpc.StringIntegerValue_Text{Text: "from client 2"}},
        },
        Type: "string",
    }
    res4, _ := client.WithRepeatedStringInt(ctx, req4)
    fmt.Printf("WithRepeatedStringInt_4 is nil: %v\n", res4.StringIntArray == nil)
    fmt.Printf("WithRepeatedStringInt_4: %v\n", res4.GetStringIntArray())

    // Mixed!!  int and string
    req5 := &rpc.WithRepeatedStringIntRequest{
        StringIntArray: []*rpc.StringIntegerValue{
            {Value: &rpc.StringIntegerValue_Number{Number: int64(543)}},
            {Value: &rpc.StringIntegerValue_Number{Number: int64(777)}},
            {Value: &rpc.StringIntegerValue_Text{Text: "from client 1"}},
            {Value: &rpc.StringIntegerValue_Text{Text: "from client 2"}},
        },
        Type: "mix",
    }
    res5, _ := client.WithRepeatedStringInt(ctx, req5)
    fmt.Printf("WithRepeatedStringInt_5 is nil: %v\n", res5.StringIntArray == nil)
    fmt.Printf("WithRepeatedStringInt_5: %v\n", res5.GetStringIntArray())
}

// server implementation
func (s GrpcTypeDefHandler) WithRepeatedStringInt(ctx context.Context, req *rpc.WithRepeatedStringIntRequest) (*rpc.WithRepeatedStringIntResponse, error) {
    fmt.Println("---------------")
    fmt.Printf("RepeatedStringInt is nil: %v\n", req.GetStringIntArray() == nil)
    fmt.Printf("RepeatedStringInt: %v\n", req.GetStringIntArray())

    switch req.GetType() {
    case "nil1":
        return &rpc.WithRepeatedStringIntResponse{}, nil
    case "nil2":
        return &rpc.WithRepeatedStringIntResponse{StringIntArray: nil}, nil
    case "empty":
        return &rpc.WithRepeatedStringIntResponse{StringIntArray: []*rpc.StringIntegerValue{}}, nil
    case "number":
        return &rpc.WithRepeatedStringIntResponse{StringIntArray: []*rpc.StringIntegerValue{
            {Value: &rpc.StringIntegerValue_Number{Number: int64(55)}},
            {Value: &rpc.StringIntegerValue_Number{Number: int64(66)}},
        }}, nil
    case "string":
        return &rpc.WithRepeatedStringIntResponse{StringIntArray: []*rpc.StringIntegerValue{
            {Value: &rpc.StringIntegerValue_Text{Text: "string1"}},
            {Value: &rpc.StringIntegerValue_Text{Text: "string2"}},
        }}, nil
    case "mix":
        return &rpc.WithRepeatedStringIntResponse{StringIntArray: []*rpc.StringIntegerValue{
            {Value: &rpc.StringIntegerValue_Number{Number: int64(55)}},
            {Value: &rpc.StringIntegerValue_Number{Number: int64(66)}},
            {Value: &rpc.StringIntegerValue_Text{Text: "string1"}},
            {Value: &rpc.StringIntegerValue_Text{Text: "string2"}},
        }}, nil
    default:
        return nil, status.Errorf(codes.InvalidArgument, fmt.Sprintf("not supported case '%s'", req.GetType()))
    }
}

The result is as follows. Both int and string are contained in the array!!

$ make runServer
2023/06/22 15:00:04 start gRPC server               | $ make runClient
---------------                                     | WithRepeatedStringInt_1 is nil: true
RepeatedStringInt is nil: true                      | WithRepeatedStringInt_1: []
RepeatedStringInt: []                               | WithRepeatedStringInt_2 is nil: true
---------------                                     | WithRepeatedStringInt_2: []
RepeatedStringInt is nil: true                      | WithRepeatedStringInt_3 is nil: false
RepeatedStringInt: []                               | WithRepeatedStringInt_3: [number:55 number:66]
---------------                                     | WithRepeatedStringInt_4 is nil: false
RepeatedStringInt is nil: false                     | WithRepeatedStringInt_4: [text:"string1" text:"string2"]
RepeatedStringInt: [number:543 number:777]          | WithRepeatedStringInt_5 is nil: false
---------------                                     | WithRepeatedStringInt_5: [number:55 number:66 text:"string1" text:"string2"]
RepeatedStringInt is nil: false                     
RepeatedStringInt: [text:"from client 1" text:"from client 2"]
---------------
RepeatedStringInt is nil: false
RepeatedStringInt: [number:543 number:777 text:"from client 1" text:"from client 2"]

map – key value pair for dictionary

Dictionary, key-value pair is also supported as map keyword.

message WithMapRequest {
  map<string, int64> map_value = 1;
  string type = 2;
}
message WithMapResponse {
  map<string, int64> map_value = 1;
}

We can set the key value by using map.

// client implementation
func (m *MiddleMan) WithMap() {
    client := rpc.NewTypesDefClient(m.conn)

    ctx := context.Background()

    req1 := &rpc.WithMapRequest{MapValue: nil, Type: "nil1"}
    res1, _ := client.WithMap(ctx, req1)
    fmt.Printf("WithMap_1 is nil: %v\n", res1.MapValue == nil)
    fmt.Printf("WithMap_1: %v\n", res1.GetMapValue())

    req2 := &rpc.WithMapRequest{MapValue: map[string]int64{"foo": 11, "bar": 22}, Type: ""}
    res2, _ := client.WithMap(ctx, req2)
    fmt.Printf("WithMap_2 is nil: %v\n", res2.MapValue == nil)
    fmt.Printf("WithMap_2: %v\n", res2.GetMapValue())
}


// server implementation
func (s GrpcTypeDefHandler) WithMap(ctx context.Context, req *rpc.WithMapRequest) (*rpc.WithMapResponse, error) {
    fmt.Println("---------------")
    fmt.Printf("Map is nil: %v\n", req.GetMapValue() == nil)
    fmt.Printf("Map: %v\n", req.GetMapValue())

    switch req.GetType() {
    case "nil1":
        return &rpc.WithMapResponse{}, nil
    case "nil2":
        return &rpc.WithMapResponse{MapValue: nil}, nil
    default:
        return &rpc.WithMapResponse{MapValue: map[string]int64{"first": 1, "second": 2, "third": 3}}, nil
    }
}

When the value is nil, an empty map can be obtained by GetXxxx().

$ make runServer                        | $ make runClient
2023/06/22 15:11:06 start gRPC server   | WithMap_1 is nil: true
---------------                         | WithMap_1: map[]
Map is nil: true                        | WithMap_2 is nil: false
Map: map[]                              | WithMap_2: map[first:1 second:2 third:3]
---------------                         |
Map is nil: false                       |
Map: map[bar:22 foo:11]                 |

Related topic

Do you want to learn more about gRPC? Check the following post to know the different function types.

Comments

Copied title and URL