1 回答

TA貢獻1840條經驗 獲得超5個贊
我找到了這個問題的兩個潛在解決方案,但它們都涉及UnmarshalJSON. 首先,我嘗試修改我的原型定義,使其results成為 類型bytes,但 JSON 反序列化失敗,因為源數(shù)據(jù)不是字符串或任何可以[]byte直接反序列化的數(shù)據(jù)。所以,我不得不自己動手:
使用結構
使用該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;
}
然后編寫了一個自定義實現(xiàn),UnmarshalJSON其工作方式如下:
// UnmarshalJSON converts JSON data into a Providers.Polygon.ArrayResponse
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]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 compatible
func 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:
使用字節(jié)
對于這個實現(xiàn),我不需要依賴任何導入的類型,所以消息本身更容易處理:
message ArrayRespone {
int32 count = 1;
string next_url = 2;
string request_id = 3;
bytes results = 4;
string status = 5;
}
這仍然需要為 開發(fā)自定義實現(xiàn)UnmarshalJSON,但該實現(xiàn)也更簡單:
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 compatible
func 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將函數(shù)修改為:
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
}
顯然,這個實現(xiàn)更簡單,需要的反序列化步驟更少,這意味著比Struct實現(xiàn)更少的反射。
- 1 回答
- 0 關注
- 259 瀏覽
添加回答
舉報