英文:
How to get a field value from FieldDescriptor and Value in protoreflect.Range?
问题
我有一个proto定义:
message Foo {
oneof bar {
BarA bar_a = 1;
BarB bar_b = 2;
}
}
message BarA {
string text = 1;
}
message BarB {
int32 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
。
更新更多信息
我实际上正在使用的第一个proto是:
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;
}
}
我的第二个proto是另一个服务+项目的一部分:
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
的oneof,以及像WelcomeEmailTemplate
这样的模板可能会更改其字段。但我想编写一个不需要针对这些更改进行更新的函数。
我相信这是可能的,参考这里的答案。
我可以在SendNotificationRequest
及其字段上嵌套md.Range
,以处理所有字段,并编写一个switch case来转换任何Field
的oneof。我还可以获取FieldOption和MessageOption。在此过程中,我构建CreateNotificationRequest
。
英文:
I have a proto:
message Foo {
oneof bar {
BarA bar_a = 1;
BarB bar_b = 2;
}
}
message BarA {
string text = 1;
}
message BarB {
int num = 1;
}
Then the code:
foo = &Foo{
Bar: &Foo_BarA{
BarA: &BarA{
Text: "text",
},
},
}
md := foo.ProtoReflect()
md.Range(func(fd protoreflect.FieldDescriptor, v protoreflect.Value) bool {
}
Inside md.Range
, how do I get the field value from the fd, v
pair? For example when it iterates over the Bar
field, I want to get a BarA
object and retrieve text
. I need to use Range
because I'm also using FieldOptions
.
Update with more info
My first proto is that I'm actually working with (the above protos are contrived examples) is:
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;
}
}
My second proto, part of another service+project, is:
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;
}
}
I want to write one function that can convert any SendNotificationRequest
to a CreateNotificationRequest
. Let's call SendNotificationRequest
's import alias api
and CreateNotificationRequest
's api2
. I cannot change api2
.
Not too relevant but I made the non-required decision to use typed Template
for my service (api
) instead of the generic map of api2
, as I felt typed classes are more maintainable and less error-prone.
For an example conversion, this
&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"}},
},
},
},
},
}
should convert to:
&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"}},
},
}
There may be new oneofs of EmailTemplate
, and templates like WelcomeEmailTemplate
may change its fields. But I want to write one function that doesn't need to be updated for those changes.
I believe this is very possible from the answer here.
I can nest md.Range
on a SendNotificationRequest
and its fields to process all its fields, and write a switch case to convert any Field
oneof. I can also fetch FieldOption and MessageOption. While this happens, I build the CreateNotificationRequest
.
答案1
得分: 2
你可以使用以下代码获取BarA
的值:
md.Range(func(fd protoreflect.FieldDescriptor, v protoreflect.Value) bool {
w := v.Message().Get(fd.Message().Fields().ByNumber(1))
fmt.Println(w) // 输出"text"
return false
})
这段代码可以直接工作,因为你的 proto 消息只有一个已填充的字段(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
})
英文:
You can get the value of BarA
with:
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
})
This works straight away because your proto message has only one populated field (bar_a
). If you have more populated fields, you should add more checks. E.g. check that the current FieldDescriptor
is the one you are looking for. A more robust implementation might look like:
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
})
Using ByName
instead of ByNumber
is debatable, since the field name doesn't play a role in the serialization, but IMO it makes for more readable code.
<hr>
As an alternative, you can directly get the concrete value of the field by type-asserting the interface held in 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
})
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论