如何在运行时在 Go 中创建 "duck-typed" grpc protobuf 消息?
How can I create a "duck-typed" grpc protobuf Message at runtime in Go?
我正在使用 grpc protobuf 消息定义并在 Go 中实现它们。
我的最终目标是让我的 rpc 在用户上检索一些 json 和 return 一个 Profile
带有可选的嵌套消息的消息,用于解组json。
使用此 rpc:
rpc GetUser (GetUserRequest) returns (Profile) {
option (google.api.http) = {
get: "/user/{id=*}"
};
}
并假设以下 json:
{
"profile": {
"foo": {
"name": "tim"
"age": 22
},
"bar": {
"level": 5
}
}
}
我想 return Profile
仅包含 "foo"、"bar" 或两者的消息,作为基于传入运行时 scope
的嵌套消息grpc 请求的参数(目前,scope
将是一个包含消息名称的字符串列表,用于将 json 子集化为相应的消息,例如 ["Foo","Bar"]
)。
给定这些消息定义:
message Profile {
//both Foo & Bar are optional by default
Foo foo = 1 [json_name="foo"];
Bar bar = 2 [json_name="bar"];
}
message Foo {
string name = 1 [json_name="name"];
int32 age = 2 [json_name="age"];
}
message Bar {
string level = 1 [json_name="level"];
}
那么在 scope
是 ["Foo"]
的情况下,我希望 rpc 为 return:
Profile{
Foo: // Foo Message unmarshalled from json
}
或者如果 "scope"
是 ["Foo","Bar"]
那么:
Profile{
Foo:
Bar:
}
问题似乎归结为 "duck-typing" 消息类型。
尝试 1
我几乎找到了使用 protoreflect
和 protoregistry
的解决方案:
import(
"google.golang.org/protobuf/reflect/protoregistry"
"google.golang.org/protobuf/reflect/protoreflect"
)
var scope protoreflect.FullName = "Foo"
var types = new(protoregistry.Types)
var message, errs = types.FindMessageByName(scope)
var almost_foo = message.New()
// using `myJson` object without top level "profile" key to make testing more simple at the moment
var myJson = `{ "foo": { .. }, "bar", { ... } }`
err = json.Unmarshal([]byte(myJson), almost_foo)
但是当我尝试使用 almost_foo
创建配置文件消息时:
var profile = &pb.Profile{almost_foo}
我得到错误:cannot use almost_foo (type protoreflect.Message) as type *package_name.Foo in field value
尝试 2
使用
import(
"github.com/jhump/protoreflect/desc"
"github.com/jhump/protoreflect/dynamic"
)
我再次尝试动态创建消息:
var fd, errs = desc.LoadFileDescriptor("github.com/package/path/..")
var message_desc = fd.FindMessage("Foo")
var almost_foo = dynamic.NewMessage(message_desc)
并出现类似的错误:
cannot use almost_foo (type *dynamic.Message) as type *package_name.Foo in field value
两次尝试几乎都创建了一个消息,但类型系统仍然不允许实际使用任何一个。
感谢任何帮助。
我设法解决了这个问题,方法是先将 json 削减到我想要的,然后通过 Unmarshal 将其放入。
我创建了一个以 Profile
作为字段的顶级消息:
message GetUserResponse {
Profile profile = 1;
}
并重新定义响应:
rpc GetUser (GetUserRequest) returns (GetUserResponse)
要构建 allowed/scoped json,我使用:
"github.com/tidwall/gjson"
"github.com/tidwall/sjson"
我将范围路径(点分隔,例如 ["profile.foo"]
)指定的 json 复制到最初为空的输出 response_json
字符串,并仅构建允许的json待退。
var response_json string
var scopes = []string{"profile.foo"}
for _, scope := range scopes {
// copy allowed json subset in raw form
value := gjson.Get(myJson, scope + "|@ugly")
// if scope didn't copy a value, skip
if value.String() == "" {
continue
} else {
// write value to output json using SetRaw since value can be an object
response_json, _ = sjson.SetRaw(response_json, scope, value.Raw)
}
}
response_json 看起来像:
{
"profile": {
"foo": {
"name": "tim"
"age": 22
}
}
}
然后解组:
response = &pb.VerifyUserResponse{}
err = json.Unmarshal([]byte(response_json), response)
我正在使用 grpc protobuf 消息定义并在 Go 中实现它们。
我的最终目标是让我的 rpc 在用户上检索一些 json 和 return 一个 Profile
带有可选的嵌套消息的消息,用于解组json。
使用此 rpc:
rpc GetUser (GetUserRequest) returns (Profile) {
option (google.api.http) = {
get: "/user/{id=*}"
};
}
并假设以下 json:
{
"profile": {
"foo": {
"name": "tim"
"age": 22
},
"bar": {
"level": 5
}
}
}
我想 return Profile
仅包含 "foo"、"bar" 或两者的消息,作为基于传入运行时 scope
的嵌套消息grpc 请求的参数(目前,scope
将是一个包含消息名称的字符串列表,用于将 json 子集化为相应的消息,例如 ["Foo","Bar"]
)。
给定这些消息定义:
message Profile {
//both Foo & Bar are optional by default
Foo foo = 1 [json_name="foo"];
Bar bar = 2 [json_name="bar"];
}
message Foo {
string name = 1 [json_name="name"];
int32 age = 2 [json_name="age"];
}
message Bar {
string level = 1 [json_name="level"];
}
那么在 scope
是 ["Foo"]
的情况下,我希望 rpc 为 return:
Profile{
Foo: // Foo Message unmarshalled from json
}
或者如果 "scope"
是 ["Foo","Bar"]
那么:
Profile{
Foo:
Bar:
}
问题似乎归结为 "duck-typing" 消息类型。
尝试 1
我几乎找到了使用 protoreflect
和 protoregistry
的解决方案:
import(
"google.golang.org/protobuf/reflect/protoregistry"
"google.golang.org/protobuf/reflect/protoreflect"
)
var scope protoreflect.FullName = "Foo"
var types = new(protoregistry.Types)
var message, errs = types.FindMessageByName(scope)
var almost_foo = message.New()
// using `myJson` object without top level "profile" key to make testing more simple at the moment
var myJson = `{ "foo": { .. }, "bar", { ... } }`
err = json.Unmarshal([]byte(myJson), almost_foo)
但是当我尝试使用 almost_foo
创建配置文件消息时:
var profile = &pb.Profile{almost_foo}
我得到错误:cannot use almost_foo (type protoreflect.Message) as type *package_name.Foo in field value
尝试 2
使用
import(
"github.com/jhump/protoreflect/desc"
"github.com/jhump/protoreflect/dynamic"
)
我再次尝试动态创建消息:
var fd, errs = desc.LoadFileDescriptor("github.com/package/path/..")
var message_desc = fd.FindMessage("Foo")
var almost_foo = dynamic.NewMessage(message_desc)
并出现类似的错误:
cannot use almost_foo (type *dynamic.Message) as type *package_name.Foo in field value
两次尝试几乎都创建了一个消息,但类型系统仍然不允许实际使用任何一个。
感谢任何帮助。
我设法解决了这个问题,方法是先将 json 削减到我想要的,然后通过 Unmarshal 将其放入。
我创建了一个以 Profile
作为字段的顶级消息:
message GetUserResponse {
Profile profile = 1;
}
并重新定义响应:
rpc GetUser (GetUserRequest) returns (GetUserResponse)
要构建 allowed/scoped json,我使用:
"github.com/tidwall/gjson"
"github.com/tidwall/sjson"
我将范围路径(点分隔,例如 ["profile.foo"]
)指定的 json 复制到最初为空的输出 response_json
字符串,并仅构建允许的json待退。
var response_json string
var scopes = []string{"profile.foo"}
for _, scope := range scopes {
// copy allowed json subset in raw form
value := gjson.Get(myJson, scope + "|@ugly")
// if scope didn't copy a value, skip
if value.String() == "" {
continue
} else {
// write value to output json using SetRaw since value can be an object
response_json, _ = sjson.SetRaw(response_json, scope, value.Raw)
}
}
response_json 看起来像:
{
"profile": {
"foo": {
"name": "tim"
"age": 22
}
}
}
然后解组:
response = &pb.VerifyUserResponse{}
err = json.Unmarshal([]byte(response_json), response)