如何在protoreflect.Range中从FieldDescriptor和value中获得字段值?



我有一个原型:

message Foo {
oneof bar {
BarA bar_a = 1;
BarB bar_b = 2;
}
}
message BarA {
string text = 1;
}
message BarB {
int num = 1;
}

然后代码:

foo = &Foo{
Bar: &Foo_BarA{
BarA: &BarA{
Text: "text",
},
},
}
md := foo.ProtoReflect()
md.Range(func(fd protoreflect.FieldDescriptor, v protoreflect.Value) bool {
}

md.Range中,我如何从fd, v对获得字段值?例如,当它迭代Bar字段时,我想获得BarA对象并检索text。我需要使用Range,因为我也在使用FieldOptions

更新更多信息

我实际使用的第一个原型(上面的原型是人为的例子)是:

extend google.protobuf.FieldOptions {
string field_name = 50000;
}
extend google.protobuf.MessageOptions {
string template_name = 50000;
Type type = 50001;
}
message SendNotificationRequest {
Type type = 1;
Template template = 2;
}
enum Type {
TYPE_UNSPECIFIED = 0;
TYPE_EMAIL = 1;
TYPE_PUSH = 2;
}
message Template {
oneof template {
EmailTemplate email_template = 1;
PushTemplate push_template = 2;
}
}
message EmailTemplate {
option (type) = TYPE_EMAIL;
oneof template {
WelcomeEmailTemplate welcome_email_template = 1;
}
}
message WelcomeEmailTemplate {
option (template_name) = "welcome_email";
Field title = 1 [(field_name) = "main_title"];
Field user_name = 2 [(field_name) = "name"];
}
message Field {
oneof value {
string str = 1;
bool bool = 2;
int64 num = 3;
}
}

我的第二个原型,另一个服务+项目的一部分,是:

message CreateNotificationRequest {
Type type = 1;
string template_name = 2;
map<string, Field> template_fields = 2; 
}
enum Type {
TYPE_UNSPECIFIED = 0;
TYPE_EMAIL = 1;
TYPE_PUSH = 2;
}
message Field {
oneof value {
string str = 1;
bool bool = 2;
int64 num = 3;
}
}

我想写一个函数,可以将任何SendNotificationRequest转换为CreateNotificationRequest。我们将SendNotificationRequest的导入别名称为api,将CreateNotificationRequest的导入别名称为api2。我不能改变api2.

不太相关,但我做了一个不必要的决定,在我的服务(api)中使用类型化的Template,而不是api2的泛型映射,因为我觉得类型化类更容易维护,更不容易出错。

对于一个示例转换,如下

&api.SendNotificationRequest{
Type: api.Type_TYPE_EMAIL,
Template:   &api.Template {
Template: &api.Template_EmailTemplate{
EmailTemplate: &api.EmailTemplate{
Template: &api.EmailTemplate_WelcomeEmailTemplate{
Title:     &api.Field{Value: &api.Placeholder_Str{Text: "Welcome Bob"}},
UserName:     &api.Field{Value: &api.Placeholder_Str{Text: "Bob Jones"}},
},
},
},
},
}

应转换为:

&api2.CreateNotificationRequest{
Type: api2.Type_TYPE_EMAIL,
TemplateName: "welcome_email",
Template:   map[string]*api2.Field{
"main_title":     &api2.Field{Value: &api.Placeholder_Str{Text: "Welcome Bob"}},
"name":     &api2.Field{Value: &api.Placeholder_Str{Text: "Bob Jones"}},
},
}

可能会有新的EmailTemplate模板,并且WelcomeEmailTemplate等模板可能会更改其字段。但是我想写一个不需要为这些变化而更新的函数。

从这里的答案我相信这是很可能的。

我可以在SendNotificationRequest和它的字段上nest md.Range来处理它的所有字段,并编写一个开关案例来转换任何Field之一。我也可以获取FieldOption和MessageOption。当这种情况发生时,我构建CreateNotificationRequest.

您可以通过:

获取BarA的值
md.Range(func(fd protoreflect.FieldDescriptor, v protoreflect.Value) bool {
w := v.Message().Get(fd.Message().Fields().ByNumber(1))
fmt.Println(w) // prints "text"
return false
})

这可以直接工作,因为您的原型消息只有一个填充字段(bar_a)。如果您有更多的填充字段,您应该添加更多的检查。例如,检查当前的FieldDescriptor是您正在寻找的。更健壮的实现可能像这样:

md.Range(func(fd protoreflect.FieldDescriptor, v protoreflect.Value) bool {
if fd.Name() == "bar_a" {
if field := fd.Message().Fields().ByName("text"); field != nil {
w := v.Message().Get(field)
fmt.Println(w) // text
return false
}
}
return true
})

使用ByName而不是ByNumber是有争议的,因为字段名在序列化中不起作用,但在我看来,它使代码更具可读性。


作为另一种选择,您可以通过类型断言v中持有的接口直接获得字段的具体值:

md.Range(func(fd protoreflect.FieldDescriptor, v protoreflect.Value) bool {
if barA, ok := v.Message().Interface().(*BarA); ok {
fmt.Println(barA.Text)
return false
}
return true
})

最新更新