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

Based on this 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;
}

I think it is a good solution to use the google.protobuf.Struct type.

The folks with their answers, helped me a lot at the beginning, so I would like to say thanks for your work! ๐Ÿ™‚ I really appreciate both solutions! ๐Ÿ™‚
On the other hand, I think I found a better one to produce these kinds of Structs.

Anuj’s Solution

This is a little bit overcomplicated but it can work.

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",
            },
        },
    },
}

Luke’s Solution

This is a shorter one but still require more conversion than necessary. map[string]interface{} -> bytes -> Struct

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

The solution from my perspective

My solution will use the official functions from the structpb package which is pretty well documented and user friendly.

Documentation: https://pkg.go.dev/google.golang.org/protobuf/types/known/structpb

For example, this code creates a *structpb.Struct via the function that was designed to do this.

m := map[string]interface{}{
    "name": "Anuj",
    "age":  23,
}

details, err := structpb.NewStruct(m) // Check to rules below to avoid errors
if err != nil {
    panic(err)
}

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

One of the most important thing, that we should keep in mind when we are building Struct from a map[string]interface{} is this:

https://pkg.go.dev/google.golang.org/protobuf/types/known/structpb#NewValue

// NewValue constructs a Value from a general-purpose Go interface.
//
//  โ•”โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•คโ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•—
//  โ•‘ Go type                โ”‚ Conversion                                 โ•‘
//  โ• โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•ชโ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•ฃ
//  โ•‘ nil                    โ”‚ stored as NullValue                        โ•‘
//  โ•‘ bool                   โ”‚ stored as BoolValue                        โ•‘
//  โ•‘ int, int32, int64      โ”‚ stored as NumberValue                      โ•‘
//  โ•‘ uint, uint32, uint64   โ”‚ stored as NumberValue                      โ•‘
//  โ•‘ float32, float64       โ”‚ stored as NumberValue                      โ•‘
//  โ•‘ string                 โ”‚ stored as StringValue; must be valid UTF-8 โ•‘
//  โ•‘ []byte                 โ”‚ stored as StringValue; base64-encoded      โ•‘
//  โ•‘ map[string]interface{} โ”‚ stored as StructValue                      โ•‘
//  โ•‘ []interface{}          โ”‚ stored as ListValue                        โ•‘
//  โ•šโ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•งโ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•
//
// When converting an int64 or uint64 to a NumberValue, numeric precision loss
// is possible since they are stored as a float64.

For example, if you would like to produce a Struct that has a string list in its JSON form, you should create the following map[string]interface{}

m := map[string]interface{}{
    "name": "Anuj",
    "age":  23,
    "cars": []interface{}{
        "Toyota",
        "Honda",
        "Dodge",
    }
}

Sorry for the long post, I hope it makes your work easier with proto3 and Go! ๐Ÿ™‚

Leave a Comment

Hata!: SQLSTATE[HY000] [1045] Access denied for user 'divattrend_liink'@'localhost' (using password: YES)