Is "google/protobuf/struct.proto" the best way to send dynamic JSON over GRPC?

Anuj Gupta picture Anuj Gupta · Oct 24, 2018 · Viewed 12.3k times · Source

I have a written a simple GRPC server and a client to call the server (both in Go). Please tell me if using golang/protobuf/struct is the best way to send a dynamic JSON with GRPC. In the example below, earlier I was creating Details as a map[string]interface{} and serializing it. Then I was sending it in protoMessage as bytes and was de-serializing the message on the server side.

Is it the best/efficient way to do it or should I define Details as a struct in my proto file?

Below is User.proto file

syntax = "proto3";
package messages;
import "google/protobuf/struct.proto";

service UserService {
    rpc SendJson (SendJsonRequest) returns (SendJsonResponse) {}
}

message SendJsonRequest {
    string UserID = 1;
    google.protobuf.Struct Details = 2;
}

message SendJsonResponse {
    string Response = 1;
}

Below is client.go file

package main
import (
    "context"
    "flag"
    pb "grpc-test/messages/pb"
    "log"
    "google.golang.org/grpc"
)

func main() {
    var serverAddr = flag.String("server_addr", "localhost:5001", "The server address in the format of host:port")
    opts := []grpc.DialOption{grpc.WithInsecure()}
    conn, err := grpc.Dial(*serverAddr, opts...)
    if err != nil {
        log.Fatalf("did not connect: %s", err)
    }
    defer conn.Close()

    userClient := pb.NewUserServiceClient(conn)
    ctx := context.Background()

    sendJson(userClient, ctx)
}

func sendJson(userClient pb.UserServiceClient, ctx context.Context) {
    var item = &structpb.Struct{
        Fields: map[string]*structpb.Value{
            "name": &structpb.Value{
                Kind: &structpb.Value_StringValue{
                    StringValue: "Anuj",
                },
            },
            "age": &structpb.Value{
                Kind: &structpb.Value_StringValue{
                    StringValue: "Anuj",
                },
            },
        },
    }

    userGetRequest := &pb.SendJsonRequest{
        UserID: "A123",
        Details: item,
    }

    res, err := userClient.SendJson(ctx, userGetRequest)
}

Answer

Luke picture Luke · Apr 23, 2020

I ended up using a two-step conversion with protojson, from map to json to struct:

m := map[string]interface{}{
  "foo":"bar",
  "baz":123,
}
b, err := json.Marshal(m)
s := &structpb.Struct{}
err = protojson.Unmarshal(b, s)

I don't find it elegant but could not really find any official documentation of how to do this differently. I also prefer to produce structures using "official" functions rather than trying to build a structure myself.