api - 在 Go 中序列化 API 响应

标签 api go serialization

作为 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{} 类型的变量.
  • 遍历生成的映射并根据其名称和动态未编码类型处理其元素,这将取决于 JSON 解析器真正看到的内容;像这样的东西:

    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
    }
    

    然后您可以定义 UnmarshalJSONapiResponse可以这样滚动:

    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/

    相关文章:

    go - 如何在接口(interface)中使用类型别名

    GO程序陷入循环

    linux - 在golang中使用IP_ADD_SOURCE_MEMBERSHIP调用SetsockoptString时出错

    java - 在 java 中序列化 Float 以供 C++ 应用程序读取的最佳方法?

    java - 如何为匿名类生成 serialVersionUID?

    api - Youtube api 上传视频不起作用 "Error : NoLinkedYouTubeAccount "

    html - 是否可以使用Web Audio API获取计算机/浏览器上当前正在播放的音频的数据?

    angular - Angular 4 中的多个顺序 API 调用

    c# - JSON.NET - 如何为作为对象类型存储在数组/列表中的原始 C# 类型包含类型名称处理

    python - 如何解决这个错误???引发HTTPError(req.full_url,代码,msg,hdrs,fp)HTTPError : Forbidden