将外部 JSON 负载反序列化为 protobuf Any

我有一个 protobuf 定义来处理来自 API 的分页结果:


message ArrayRespone {

    int32 count = 1;

    string next_url = 2;

    string request_id = 3;

    repeated google.protobuf.Any results = 4;

    string status = 5;

}

此处的目标是反序列化来自此 API 的分页响应,然后将每个页面的结果提取到适当类型的切片中。我在 Go 中编写了执行此操作的代码:


func getData[T ~proto.Message](data []byte) ([]T, error) {


    var resp *ArrayRespone

    if err := json.Unmarshal(data, &resp); err != nil {

        return nil, err

    }

    

    var items []T

    for _, result := range resp.Results {

        var item T

        if err := result.UnmarshalTo(item); err != nil {

            return nil, err

        }


        items = append(items, item)

    }


    return items, nil

}

我遇到的问题是,在测试这段代码时,我遇到了以下错误:


原型:不匹配的消息类型:得到“X”,想要“”


由此,我可以理解 Protobuf 没有必要的信息来确定它使用的是哪种类型。查看 的定义Any,我可以看到它有一个TypeUrl字段和一个Value字段。类型 URL 似乎为空但不应该为空。所以,我的想法是,如果我将它设置为X,错误就会消失,但这也行不通,因为该Value字段仍然是空的;我的 JSON 数据被忽略了。


我怎样才能让这段代码工作?


FFIVE
浏览 134回答 1
1回答

慕斯709654

我找到了这个问题的两个潜在解决方案,但它们都涉及UnmarshalJSON. 首先,我尝试修改我的原型定义,使其results成为 类型bytes,但 JSON 反序列化失败,因为源数据不是字符串或任何可以[]byte直接反序列化的数据。所以,我不得不自己动手:使用结构使用该google.protobuf.Struct类型,我将我的修改ArrayResponse为如下所示:message ArrayRespone {    int32 count = 1;    string next_url = 2;    string request_id = 3;    repeated google.protobuf.Struct results = 4;    string status = 5;}然后编写了一个自定义实现,UnmarshalJSON其工作方式如下:// UnmarshalJSON converts JSON data into a Providers.Polygon.ArrayResponsefunc (resp *ArrayRespone) UnmarshalJSON(data []byte) error {    // First, deserialize the JSON into a mapping between key fields and values    // If this fails then return an error    var mapped map[string]interface{}    if err := json.Unmarshal(data, &mapped); err != nil {        return fmt.Errorf("failed to perform first-pass unmarshal, error: %v", err)    }    // Next, extract the count from the mapping; if this fails return an error    if err := extractValue(mapped, "count", &resp.Count); err != nil {        return err    }    // Extract the next URL from the mapping; if this fails return an error    if err := extractValue(mapped, "next_url", &resp.NextUrl); err != nil {        return err    }    // Extract the request ID from the mapping; if this fails return an error    if err := extractValue(mapped, "request_id", &resp.RequestId); err != nil {        return err    }    // Extract the status from the mapping; if this fails return an error    if err := extractValue(mapped, "status", &resp.Status); err != nil {        return err    }    // Now, extract the results array into a temporary variable; if this fails return an error    var results []interface{}    if err := extractValue(mapped, "results", &results); err != nil {        return err    }    // Finally, iterate over each result and add it to the slice of results by attempting    // to convert it to a Struct; if any of these fail to convert then return an error    resp.Results = make([]*structpb.Struct, len(results))    for i, result := range results {        if value, err := structpb.NewStruct(result.(map[string]interface{})); err == nil {            resp.Results[i] = value        } else {            return fmt.Errorf("failed to create struct from result %d, error: %v", i, err)        }    }    return nil}// Helper function that attempts to extract a value from a standard mapping of interfaces// and set a field with it if the types are compatiblefunc extractValue[T any](mapping map[string]interface{}, field string, value *T) error {    if raw, ok := mapping[field]; ok {        if inner, ok := raw.(T); ok {            *value = inner        } else {            return fmt.Errorf("failed to set value %v to field %s (%T)", raw, field, *value)        }    }    return nil}然后,在我的服务代码中,我修改了代码的解组部分以使用Struct对象。此代码依赖于mapstructure包:func getData[T ~proto.Message](data []byte) ([]T, error) {    var resp *ArrayRespone    if err := json.Unmarshal(data, &resp); err != nil {        return nil, err    }        items := make([]T, len(resp.Results))    for i, result := range resp.Results {        var item T        if err := mapstructure.Decode(result.AsMap(), &item); err != nil {            return nil, err        }        items[i] = item    }    return items, nil}只要您的所有字段都可以轻松反序列化为google.protobuf.Value类型上的字段,这就可以工作。但是,对我来说情况并非如此,因为我调用的类型中的几个字段getData具有UnmarshalJSON. 所以,我实际选择的解决方案是改为使用bytes:使用字节对于这个实现,我不需要依赖任何导入的类型,所以消息本身更容易处理:message ArrayRespone {    int32 count = 1;    string next_url = 2;    string request_id = 3;    bytes results = 4;    string status = 5;}这仍然需要为 开发自定义实现UnmarshalJSON,但该实现也更简单:func (resp *ArrayRespone) UnmarshalJSON(data []byte) error {    // First, deserialize the JSON into a mapping between key fields and values    // If this fails then return an error    var mapped map[string]*json.RawMessage    if err := json.Unmarshal(data, &mapped); err != nil {        return fmt.Errorf("failed to perform first-pass unmarshal, error: %v", err)    }    // Next, extract the count from the mapping; if this fails return an error    if err := extractValue(mapped, "count", &resp.Count); err != nil {        return err    }    // Extract the next URL from the mapping; if this fails return an error    if err := extractValue(mapped, "next_url", &resp.NextUrl); err != nil {        return err    }    // Extract the request ID from the mapping; if this fails return an error    if err := extractValue(mapped, "request_id", &resp.RequestId); err != nil {        return err    }    // Extract the status from the mapping; if this fails return an error    if err := extractValue(mapped, "status", &resp.Status); err != nil {        return err    }    // Finally, iterate over each result and add it to the slice of results by attempting    // to convert it to a Struct; if any of these fail to convert then return an error    if raw, ok := mapped["results"]; ok {        resp.Results = *raw    }    return nil}// Helper function that attempts to extract a value from a standard mapping of interfaces// and set a field with it if the types are compatiblefunc extractValue[T any](mapping map[string]*json.RawMessage, field string, value *T) error {    if raw, ok := mapping[field]; ok {        if err := json.Unmarshal(*raw, &value); err != nil {            return fmt.Errorf("failed to set value %s to field %s (%T)", *raw, field, *value)        }    }    return nil}然后,我getData将函数修改为:func getData[T ~proto.Message](data []byte) ([]T, error) {    var resp *ArrayRespone    if err := json.Unmarshal(data, &resp); err != nil {        return nil, err    }        var items []T    if err := json.Unmarshal(resp.Results, &items); err != nil {        return nil, err    }    return items, nil}显然,这个实现更简单,需要的反序列化步骤更少,这意味着比Struct实现更少的反射。
打开App,查看更多内容
随时随地看视频慕课网APP