作为 Go 新手,我还没有找到解决问题的方法。我正在使用一个给出不一致响应的 API。以下是 API 给出的两个示例响应:
{
"key_a": "0,12",
"key_b": "0,1425",
"key_c": 9946
}
和
{
"key_a": 3.65,
"key_b": 3.67,
"key_c": 2800
}
我面临的问题是,在我的数据类型中,我无法处理模棱两可的数据类型。这是我的数据类型:
type apiResponse struct {
Key_a float64 `json:"key_a"`
Key_b float64 `json:"key_b"`
Key_c int `json:"key_c"`
}
这是调用 API 的代码的简化版本:
func callAPI() (apiResponse, error) {
var a apiResponse
req, err := http.NewRequest("GET", "https://www.apiurl.com", nil)
client := &http.Client{}
resp, err := client.Do(req)
data, err := ioutil.ReadAll(resp.Body)
json.Unmarshal(data, &a)
return a, err
}
如何处理 API 响应中更改的数据类型,以确保我可以在其余代码中使用这些值?
最佳答案
有多种方法可以解决这个问题。
理解这个想法的最简单的方法是使用 encoding/json
的事实。 unmarshaler 检查接收变量的类型是否实现 encoding/json.Unmarshaler
接口(interface),如果是,它调用该类型的 UnmarshalJSON
方法将原始数据传递给它,否则它会尝试解释自己。该方法负责采用它喜欢的任何方法将源原始字节解释为 JSON 文档并填充它被调用的变量。
我们可以利用它来尝试查看原始输入数据是否以 "
开头。字节(所以它是一个字符串)或不是(所以它应该是一个浮点数)。
为此,我们将创建一个自定义类型 kinkyFloat
,实现 encoding/json.Unmarshaler
界面:
package main
import (
"bytes"
"encoding/json"
"errors"
"fmt"
)
type apiResponse struct {
Key_a kinkyFloat `json:"key_a"`
Key_b kinkyFloat `json:"key_b"`
Key_c int `json:"key_c"`
}
type kinkyFloat float64
func (kf *kinkyFloat) UnmarshalJSON(b []byte) error {
if len(b) == 0 {
return errors.New("empty input")
}
if b[0] == '"' {
// Get the data between the leading and trailing " bytes:
b = b[1 : len(b)-1]
if i := bytes.IndexByte(b, ','); i >= 0 {
b[i] = '.'
}
}
// At this point, we have b containing a set of bytes without
// encolsing "-s and with the decimal point presented by a dot.
var f float64
if err := json.Unmarshal(b, &f); err != nil {
return err
}
*kf = kinkyFloat(f)
return nil
}
func main() {
for _, input := range []string{
`{"Key_a": "0,12", "Key_b": "12,34", "Key_c": 42}`,
`{"Key_a": 0.12, "Key_b": 12.34, "Key_c": 42}`,
} {
var resp apiResponse
err := json.Unmarshal([]byte(input), &resp)
if err != nil {
fmt.Println("error: ", err)
continue
}
fmt.Println("OK: ", resp)
}
}
可以看到,unmarshaling 方法检查原始数据是否通过
它以
"
开头byte,如果是,它首先去掉封闭的双引号,然后替换所有 ,
-s 与 .
-s — 使更新后的原始数据看起来像一个正确的 JSON 格式的浮点数。如果原始数据不以双引号开头,则不会以任何方式触及它。
毕竟,我们调用了
encoding/json
的解码代码。我们自己——告诉它再次解码我们的字节 block ;请注意有关此调用的两件事:float64
的变量传递给它。 ,而不是 kinkyFloat
——否则我们最终会递归调用自定义解码方法,最终导致堆栈溢出。 这种方法的一个警告是结果结构的字段类型为
kinkyFloat
。 ,而不是简单的 float64
,这可能导致需要在代码中到处溢出类型转换,以便在算术表达式中使用它们。如果这不方便,还有其他方法可以解决问题。
通常的方法是定义
UnmarshalJSON
在目标上struct
键入自己,然后像这样滚动:map[string]interface{}
类型的变量. var resp apiResponse
for k, v := range resultingMap {
var err error
switch k {
case "Key_a":
resp.Key_a, err = toFloat64(v)
case "Key_b":
resp.Key_b, err = toFloat64(v)
case "Key_c":
resp.Key_c = v.(int)
}
if err != nil {
return err
}
}
…在哪里
toFloat64
定义如下:func toFloat64(input interface{}) (float64, error) {
switch v := input.(type) {
case float64:
return v, nil
case string:
var f float64
// parse the string as in the code above.
return f, nil
default:
return 0, fmt.Errorf("invalid type: %T", input)
}
}
另一种方法是有一对用于解码的结构:一个看起来像
type apiResponse struct {
Key_a float64
Key_b float64
Key_c int
}
另一个专门用于解码:
type apiRespHelper struct {
Key_a kinkyFloat
Key_b kinkyFloat
Key_c int
}
然后您可以定义
UnmarshalJSON
在 apiResponse
可以这样滚动:func (ar *apiResponse) UnmarshalJSON(b []byte) error {
var raw apiRespHelper
if err := json.Unmarshal(b, &raw); err != nil {
return err
}
*ar = apiResponse{
Key_a: float64(raw.Key_a),
Key_b: float64(raw.Key_b),
Key_c: raw.Key_c,
}
return nil
}
由于这两种类型都具有其字段类型的兼容内存表示,因此可以进行简单的类型转换。
更新:不幸的是,简单的转换——如
*ar = apiResponse(raw)
— 即使 struct
的字段都不起作用类型具有兼容的内存表示(可以相互类型转换,成对),因此必须使用赋值助手,它可以单独对每个字段进行类型转换,或者像示例中那样对结构字面量进行类型转换。
关于api - 在 Go 中序列化 API 响应,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/59918582/