如何从protoreflect.Range的fd与v中获取字段值并实现proto请求转换
1. Range回调中获取oneof字段值的基础方法
当Range遍历到oneof类型的字段时,通过v.Message().Interface()即可拿到oneof中实际存储的结构体实例,示例代码如下:
md.Range(func(fd protoreflect.FieldDescriptor, v protoreflect.Value) bool { // 判断当前字段属于oneof if fd.ContainingOneof() != nil { // 取出oneof中实际的消息对象,转成对应类型 switch msg := v.Message().Interface().(type) { case *BarA: // 直接读取BarA的属性 fmt.Println(msg.Text) case *BarB: fmt.Println(msg.Num) } } return true })
2. 业务场景通用转换实现
通过递归反射遍历+读取自定义Option的方式,完全可以实现无需同步适配后续模板新增/字段变更的转换逻辑,具体实现如下:
前置依赖
import ( "google.golang.org/protobuf/reflect/protoreflect" "google.golang.org/protobuf/proto" // 替换为实际的proto生成包路径 "your/package/path/api" "your/package/path/api2" ) // 预获取自定义扩展的字段标识 var ( E_FieldName = proto.GetExtensionDesc(((*google.protobuf.FieldOptions)(nil)), "field_name") E_TemplateName = proto.GetExtensionDesc(((*google.protobuf.MessageOptions)(nil)), "template_name") )
转换函数实现
func ConvertSendToCreate(req *api.SendNotificationRequest) *api2.CreateNotificationRequest { // 初始化目标对象 res := &api2.CreateNotificationRequest{ Type: api2.Type(req.Type.Number()), // 枚举值一一对应直接转换 TemplateFields: make(map[string]*api2.Field), } // 定义递归遍历反射消息的函数 var traverse func(m protoreflect.Message) traverse = func(m protoreflect.Message) { // 读取当前消息的template_name Option,赋值给目标对象的模板名 if ext, err := proto.GetExtension(m.Descriptor().Options(), E_TemplateName); err == nil { if tplName, ok := ext.(string); ok && tplName != "" { res.TemplateName = tplName } } // 遍历当前消息的所有字段 m.Range(func(fd protoreflect.FieldDescriptor, v protoreflect.Value) bool { // 情况1:当前字段配置了field_name Option,需要放入模板字段map if ext, err := proto.GetExtension(fd.Options(), E_FieldName); err == nil { if fieldName, ok := ext.(string); ok && fieldName != "" { apiField := v.Message().Interface().(*api.Field) res.TemplateFields[fieldName] = convertField(apiField) return true } } // 情况2:当前字段是消息/oneof类型,递归遍历 if fd.Kind() == protoreflect.MessageKind { traverse(v.Message()) } return true }) } // 从请求根对象开始遍历 traverse(req.ProtoReflect()) return res } // 转换两个服务的Field结构,结构完全一致直接拷贝oneof值即可 func convertField(f *api.Field) *api2.Field { res := &api2.Field{} switch v := f.Value.(type) { case *api.Field_Str: res.Value = &api2.Field_Str{Str: v.Str} case *api.Field_Bool: res.Value = &api2.Field_Bool{Bool: v.Bool} case *api.Field_Num: res.Value = &api2.Field_Num{Num: v.Num} } return res }
方案特性
- 所有逻辑依赖proto的Option配置和反射遍历,后续新增模板类型、模板新增字段,只要正确配置
(template_name)和(field_name),转换函数无需任何修改 - 枚举直接通过数值映射,只要两边枚举定义的数值一致即可正常工作
- 如果后续Field的oneof新增类型,仅需在
convertField中新增对应case即可,无需修改主转换逻辑
内容的提问来源于stack exchange,提问作者onepiece
相关产品推荐
相关产品推荐

