本文详解 go 程序调用 ip-api.com json api 时响应体为空、结构体解析失败的两大核心问题:未正确读取响应 body 及结构体字段未导出+缺少 json 标签映射,并提供完整可运行示例。
本文详解 go 程序调用 ip-api.com json api 时响应体为空、结构体解析失败的两大核心问题:未正确读取响应 body 及结构体字段未导出+缺少 json 标签映射,并提供完整可运行示例。
在 Go 中调用外部 HTTP API(如 https://www.php.cn/link/434f2774582010201d7784fa906e546a)时,看似简单的 http.Get 却常因两个隐蔽但关键的错误导致“响应状态 200 正常,但 body 为空、解析失败”。你遇到的问题正是典型代表:resp.Body 在 json.NewDecoder(resp.Body).Decode() 后已 EOF,再调用 ioutil.ReadAll(resp.Body) 必然返回空字节;同时,结构体字段全为小写(未导出),JSON 解码器无法赋值——二者叠加,最终得到一个零值结构体和空响应体输出。
✅ 正确做法:顺序 + 导出 + 标签
首先,必须先完整读取响应 Body,再解码。json.Decoder 会流式消费 io.Reader,一旦读完,resp.Body 就不可重用。因此不能在 Decode() 后再 ReadAll() —— 应该统一用 ioutil.ReadAll(Go 1.16+ 推荐 io.ReadAll)读取原始字节,再用 json.Unmarshal 解析:
body, err := io.ReadAll(resp.Body)
if err != nil {
return IpResponseSuccess{}, err
}
defer resp.Body.Close() // 注意:Close 仍需调用,即使已读完
err = json.Unmarshal(body, &ip_response)
if err != nil {
return IpResponseSuccess{}, err
}
其次,结构体字段必须导出(首字母大写)且通过 json tag 显式映射。Go 的 encoding/json 只能序列化/反序列化导出字段(即首字母大写)。原代码中 as, city 等均为小写私有字段,解码器完全忽略它们,导致结构体始终为零值。
修正后的结构体如下(同时将 lat/lon 改为 float64,匹配 API 实际返回数值类型):
type IpResponseSuccess struct {
As string `json:"as"`
City string `json:"city"`
Country string `json:"country"`
CountryCode string `json:"countryCode"`
Isp string `json:"isp"`
Lat float64 `json:"lat"`
Lon float64 `json:"lon"`
Org string `json:"org"`
Query string `json:"query"`
Region string `json:"region"`
RegionName string `json:"regionName"`
Status string `json:"status"`
Timezone string `json:"timezone"`
Zip string `json:"zip"`
}
⚠️ 注意:ioutil 已在 Go 1.16 中弃用,请使用 io/ioutil 的替代方案 io 包。完整示例基于 Go 1.19+:
package main
import (
"encoding/json"
"fmt"
"io"
"net/http"
)
type IpResponseSuccess struct {
As string `json:"as"`
City string `json:"city"`
Country string `json:"country"`
CountryCode string `json:"countryCode"`
Isp string `json:"isp"`
Lat float64 `json:"lat"`
Lon float64 `json:"lon"`
Org string `json:"org"`
Query string `json:"query"`
Region string `json:"region"`
RegionName string `json:"regionName"`
Status string `json:"status"`
Timezone string `json:"timezone"`
Zip string `json:"zip"`
}
func Query(url string) (IpResponseSuccess, error) {
resp, err := http.Get(url)
if err != nil {
return IpResponseSuccess{}, err
}
defer resp.Body.Close() // 始终确保关闭
body, err := io.ReadAll(resp.Body)
if err != nil {
return IpResponseSuccess{}, err
}
var ipResp IpResponseSuccess
if err := json.Unmarshal(body, &ipResp); err != nil {
return IpResponseSuccess{}, fmt.Errorf("failed to parse JSON: %w", err)
}
// 可选:调试输出原始响应
// fmt.Printf("Raw response: %s\n", body)
return ipResp, nil
}
func main() {
ip, err := Query("http://ip-api.com/json")
if err != nil {
fmt.Printf("Error: %v\n", err)
return
}
fmt.Printf("Location: %s, %s — Lat: %.4f, Lon: %.4f\n", ip.City, ip.Country, ip.Lat, ip.Lon)
}
? 额外建议与注意事项
- 不要依赖 Content-Length 判断 body 是否为空:HTTP/1.1 持久连接下,resp.Body 是惰性读取流,不读就不会触发传输。
- 检查响应状态码:虽然本例是 200,但生产环境务必添加 if resp.StatusCode != http.StatusOK 校验。
- 考虑超时与上下文:使用 http.Client 配置 Timeout 或 context.WithTimeout,避免请求无限挂起。
- API 限频提醒:ip-api.com 免费版有请求频率限制(约 45 次/分钟),高频调用需加缓存或升级计划。
- HTTPS 优先:官方推荐使用 https://www.php.cn/link/434f2774582010201d7784fa906e546a,更安全可靠。
遵循以上两点核心修正(读取顺序 + 结构体导出+tag),你的 Go 程序即可稳定、准确地获取 IP 地理位置数据。
文章来自机圈观察员网,发布者:,转载请注明出处:https://www.jqgcy.com/shoujipingce/125072.html