Golang中关于Protoreflect的问题求助

Golang中关于Protoreflect的问题求助 我有一个gRPC服务,它接收特定格式的请求和响应。

message Response {
     string ID = 1;
     ScorerA ScrA = 2;
     ScorerB ScrB = 3;
}
// 在响应中需要填充 ScorerA 和 ScorerB 中的任意一个,这将在运行时决定

message ScorerA {
    string Name = 1;
}

message ScorerB {
    string Section = 1;
}

我创建了一个从评分器名称到评分器类型的映射:

var scrA *pb.ScorerA = &pb.ScorerA{}
var scrAType protoreflect.MessageType = scrA.ProtoReflect().Type()

var scrB *pb.ScorerB = &pb.ScorerB{}
var scrBType protoreflect.MessageType = scrB.ProtoReflect().Type()

ScorerTypes := map[string]protoreflect.MessageType {
     "ScorerA":  scrAType
     "ScorerB":  scrBType
}

在我的gRPC服务器中,我根据某些条件在运行时获取评分器名称。

scorerName := "ScorerA" // 假设如此

现在,我创建一个类型为 scrAType 的空结构(不要与 Go 结构体混淆)并为字段设置值:

var recType protoreflect.MessageType = ScorerTypes[scorerName]
var rec protoreflect.Message = recType.New()
.
.
.
rec.Set(_, _) // 为字段设置值

最后使用以下方式转换为 protoreflect.ProtoMessage:

var finalScorer protoreflect.ProtoMessage = rec.Interface()

参考:https://pkg.go.dev/google.golang.org/protobuf/reflect/protoreflect#hdr-Go_Type_Descriptors

现在我的gRPC服务器的返回类型是 *pb.Response,所以:

resp := &pb.Response{ID: "1", ScrA: finalScorer}

这会产生以下错误:

cannot use finalScorer (variable of type protoreflect.ProtoMessage) as *"proto".ScorerA value in struct literal

如何将 protoreflect.ProtoMessage 转换为实际的 pb(例如 pb.ScorerA)? 如果有更好的方法来处理这个用例,请告诉我。


更多关于Golang中关于Protoreflect的问题求助的实战教程也可以访问 https://www.itying.com/category-94-b0.html

3 回复
rsp := &pb.Response{ID: "1"}

rspMsg := rsp.ProtoReflect()
rspType := rspMsg.Type()
rspFields := rspType.Descriptor().Fields()

rv := protoreflect.ValueOf(rsp.ProtoReflect())
rv.Message().Set(rspFields.ByTextName("ScrA"), protoreflect.ValueOf(finalScorer.ProtoReflect()))

更多关于Golang中关于Protoreflect的问题求助的实战系列教程也可以访问 https://www.itying.com/category-94-b0.html


问题是否可能在于你正在初始化嵌套结构。

GeeksforGeeks 图标

Golang 中的嵌套结构 - GeeksforGeeks

一个面向极客的计算机科学门户。它包含撰写精良、经过深思熟虑和详细解释的计算机科学和编程文章、测验以及实践/竞争性编程/公司面试问题。

在不编译的情况下,这样能行吗:

resp := &pb.Response{ID: "1", ScrA:ScorerA{finalScorer}}

如果不行,尝试在初始化之外设置 resp 的字段。

根据你的描述,问题在于需要将 protoreflect.ProtoMessage 转换为具体的协议缓冲区类型。以下是解决方案:

解决方案:使用类型断言

// 创建响应
resp := &pb.Response{ID: "1"}

// 根据 finalScorer 的实际类型进行类型断言
switch scorer := finalScorer.(type) {
case *pb.ScorerA:
    resp.ScrA = scorer
case *pb.ScorerB:
    resp.ScrB = scorer
default:
    // 处理未知类型的情况
    return nil, fmt.Errorf("unknown scorer type: %T", finalScorer)
}

完整示例代码

package main

import (
    "fmt"
    "google.golang.org/protobuf/reflect/protoreflect"
    pb "your/proto/package" // 替换为实际的包路径
)

func main() {
    // 初始化类型映射
    var scrA *pb.ScorerA = &pb.ScorerA{}
    var scrAType protoreflect.MessageType = scrA.ProtoReflect().Type()

    var scrB *pb.ScorerB = &pb.ScorerB{}
    var scrBType protoreflect.MessageType = scrB.ProtoReflect().Type()

    ScorerTypes := map[string]protoreflect.MessageType{
        "ScorerA": scrAType,
        "ScorerB": scrBType,
    }

    // 运行时决定评分器类型
    scorerName := "ScorerA"
    recType := ScorerTypes[scorerName]
    
    if recType == nil {
        panic("unknown scorer type")
    }

    // 创建新的消息实例
    rec := recType.New()
    
    // 设置字段值
    fields := rec.Descriptor().Fields()
    if nameField := fields.ByName("Name"); nameField != nil && nameField.Kind() == protoreflect.StringKind {
        rec.Set(nameField, protoreflect.ValueOfString("ExampleName"))
    }
    if sectionField := fields.ByName("Section"); sectionField != nil && sectionField.Kind() == protoreflect.StringKind {
        rec.Set(sectionField, protoreflect.ValueOfString("ExampleSection"))
    }

    // 转换为 ProtoMessage
    finalScorer := rec.Interface().(protoreflect.ProtoMessage)

    // 创建响应并设置正确的字段
    resp := &pb.Response{ID: "1"}

    // 类型断言
    switch scorer := finalScorer.(type) {
    case *pb.ScorerA:
        resp.ScrA = scorer
    case *pb.ScorerB:
        resp.ScrB = scorer
    default:
        panic(fmt.Sprintf("unexpected type: %T", finalScorer))
    }

    // 现在 resp 可以正确返回
    fmt.Printf("Response: %+v\n", resp)
}

替代方案:使用反射直接赋值

如果你需要更通用的解决方案,可以使用反射:

func setScorerField(resp *pb.Response, scorer protoreflect.ProtoMessage) error {
    v := reflect.ValueOf(resp).Elem()
    
    switch s := scorer.(type) {
    case *pb.ScorerA:
        field := v.FieldByName("ScrA")
        if field.IsValid() && field.CanSet() {
            field.Set(reflect.ValueOf(s))
            return nil
        }
    case *pb.ScorerB:
        field := v.FieldByName("ScrB")
        if field.IsValid() && field.CanSet() {
            field.Set(reflect.ValueOf(s))
            return nil
        }
    }
    
    return fmt.Errorf("cannot set scorer field for type %T", scorer)
}

// 使用方式
resp := &pb.Response{ID: "1"}
if err := setScorerField(resp, finalScorer); err != nil {
    return nil, err
}

注意事项

  1. 类型安全:类型断言是类型安全的,但需要处理所有可能的类型
  2. 性能:类型断言比反射性能更好
  3. 可扩展性:如果未来有更多评分器类型,需要更新 switch 语句

这种方法允许你在运行时动态创建和设置不同类型的评分器,同时保持类型安全。

回到顶部