17370845950

如何用泛型函数统一处理 HTTP GET 请求与 JSON 反序列化

本文介绍一种简洁、类型安全的 go 语言通用 http get + json 解析封装方案,通过接收指针参数避免反射或类型断言,消除重复逻辑,同时保持编译时类型检查。

在构建 API 客户端时,大量重复的 HTTP GET → 响应校验 → 读取 Body → JSON 反序列化流程极易导致代码冗余和维护困难。核心痛点在于:只有目标结构体类型不同,其余逻辑完全一致。此时,一个类型安全、无需 interface{} 类型断言、也不依赖 reflect 的泛型式封装是最优解。

Go 1.18+ 推荐使用泛型(更现代、更安全),但即使在旧版本中,也可通过传入指向目标变量的指针实现零成本抽象。关键在于:json.Unmarshal 本身要求第二个参数是 interface{},且该接口值必须是一个可寻址的指针(如 &users),而非指针的地址(如 &target)。常见错误正是误将 interface{} 变量取地址,导致反序列化写入的是 target 的副本,而非原始变量。

✅ 正确做法如下(兼容 Go 1.17+):

import (
    "encoding/json"
    "io"
    "net/http"
    "errors"
)

// request 执行 GET 请求,将响应 JSON 解析到 target 指向的变量中
// target 必须为指向切片或结构体的指针,例如 &[]User{} 或 &Project{}
func request(url string, target interface{}) error {
    resp, err := http.Get(url)
    if err != nil {
        return err
    }
    defer resp.Body.Close()

    if resp.StatusCode != http.StatusOK {
        return errors.New("non-200 status code: " + resp.Status)
    }

    body, err := io.ReadAll(resp.Body)
    if err != nil {
        return err
    }

    return json.Unmarshal(body, target) // ✅ 直接传 target(它已是 *[]User 等)
}

使用方式清晰直观:

func getUsers(url string) ([]User, error) {
    var users []User
    if err := request(url, &users); err != nil {
        return nil, err
    }
    return users, nil
}

func getProject(url string) (Project, error) {
    var proj Project
    if err := request(url, &proj); err != nil {
        return Project{}, err
    }
    return proj, nil
}

⚠️ 注意事项:

  • target 参数必须传指针(&users),否则 json.Unmarshal 会报错 json: Unmarshal(nil *[]User);
  • 不要尝试在函数内对 target 再取地址(如 &target),这会导致解包到临时接口变量,原变量不变;
  • 错误处理建议按需增强:可返回 *http.Response 供调用方检查 Header/Status,或封装自定义错误类型(如含 StatusCode 字段);
  • 生产环境推荐使用带超时的 http.Client 替代 http.Get,例如:
    client := &http.Client{Timeout: 10 * time.Second}
    resp, err := client.Get(url)

? 进阶提示(Go 1.18+):若项目已升级,可进一步改用泛型函数,获得更强的类型推导与 IDE 支持:

func Get[T any](url string) (T, error) {
    var result T
    if err := request(url, &result); err != nil {
        return result, err
    }
    return result, nil
}
// 使用:users, err := Get[[]User](url)

该模式彻底解耦网络层与业务数据结构,让每个 API 调用仅关注「URL 是什么」和「我要什么类型」,显著提升可读性、可测性与可维护性。