Является ли google/protobuf/struct.proto лучшим способом отправки динамического JSON через GRPC?

У меня написан простой сервер GRPC и клиент для вызова сервера (оба на Go). Скажите, пожалуйста, является ли использование golang/protobuf/struct лучшим способом отправки динамического JSON с помощью GRPC. В приведенном ниже примере ранее я создавал Details как map[string]interface{} и сериализовал его. Затем я отправлял его в protoMessage как bytes и десериализовал сообщение на стороне сервера.

Это лучший/эффективный способ сделать это или я должен определить Details как структуру в моем прото-файле?

Ниже приведен файл User.proto.

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

Ниже приведен файл client.go

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

person Anuj Gupta    schedule 24.10.2018    source источник


Ответы (3)


На основе этого прото-файла.

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

Я думаю, что это хорошее решение для использования типа google.protobuf.Struct.

Ребята своими ответами очень помогли мне в начале, поэтому я хотел бы поблагодарить вас за вашу работу! :) Я очень ценю оба решения! :) С другой стороны, я думаю, что нашел лучший для производства таких Structs.

Решение Анужа

Это немного сложно, но это может сработать.

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

Решение Люка

Это короче, но все же требует большего преобразования, чем необходимо. 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)

Решение с моей точки зрения

Мое решение будет использовать официальные функции из пакета structpb, который довольно хорошо документирован и удобен для пользователя.

Документация: https://pkg.go.dev/google.golang.org/protobuf/types/known/structpb

Например, этот код создает *structpb.Struct с помощью функции, предназначенной для этого.

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

Одна из самых важных вещей, которую мы должны иметь в виду, когда мы строим Struct из map[string]interface{}, заключается в следующем:

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.

Например, если вы хотите создать Struct со списком строк в форме JSON, вам следует создать следующий map[string]interface{}

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

Извините за длинное сообщение, надеюсь, это облегчит вам работу с proto3 и Go! :)

person F. Norbert    schedule 26.11.2020

В итоге я использовал двухэтапное преобразование с protojson, из карты в json в структуру:

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

Я не нахожу это элегантным, но не смог найти официальной документации о том, как сделать это по-другому. Я также предпочитаю создавать структуры, используя «официальные» функции, а не пытаться построить структуру самостоятельно.

person Luke    schedule 23.04.2020

Если у вас уже есть данные JSON, вы также можете закодировать их как строковое поле. В противном случае использование google.protobuf.Struct кажется вполне разумным, и вы сможете использовать jsonpb для простого преобразования между Struct и JSON на клиенте и сервере.

person Doug Fawley    schedule 31.10.2018
comment
Любой пример того, как использовать jsobpb для типа структуры прототипа, будет полезен. - person Arajit; 07.01.2020
comment
@Arajit 1) struct -> []byte // с использованием json.Unmarshal. 2) proto struct -> []byte // с использованием пакета jsonpb; bts:= buffers.NewBuffer(nil) jsonpb.Unmarshaler{}.Marshal(bts, yourProto). 3) []byte -> proto struct // с использованием пакета jsonpb; jsonpb.Unmarshal(bytes.NewBuffer(bts), &protoStruct{}) - person Long Tran; 11.05.2021