
Go语言通过标准库 encoding/json 提供了强大的JSON编码(Marshal)和解码(Unmarshal)功能。json.Unmarshal 函数用于将JSON格式的字节切片解析到Go语言的结构体、映射或切片中。其基本原理是通过反射机制,将JSON对象中的键与Go结构体中导出(首字母大写)的字段进行匹配。如果结构体字段与JSON键名不一致,可以通过结构体标签(json:"key_name")来指定映射关系。
在与外部API(例如Google翻译API)交互时,我们通常会接收到JSON格式的响应。然而,即使确认API返回了正确的JSON数据,json.Unmarshal 却可能返回一个空或不完整的结构体,导致数据无法正常使用。
考虑以下Google翻译API的JSON响应示例:
{
"data": {
"translations": [
{
"translatedText": "Mi nombre es John, nació en Nairobi y tengo 31 años de edad",
"detectedSourceLanguage": "en"
}
]
}
}为了解析上述JSON,我们可能初步定义了如下Go结构体:
立即学习“go语言免费学习笔记(深入)”;
type Translation struct {
Data string
Translations []struct {
TranslatedText string
SourceLanguage string // 期望映射到 detectedSourceLanguage
}
}以及相应的API调用和反序列化逻辑:
// ... (InputText struct and other setup omitted for brevity)
func (i *InputText) TranslateString() (*Translation, error) {
// ... (HTTP request setup)
getResp, err := http.Get(u)
if err != nil {
log.Fatal("error", err)
return nil, err
}
defer getResp.Body.Close()
body, err := io.ReadAll(getResp.Body) // 使用 io.ReadAll
if err != nil {
log.Fatal("error reading response body", err)
return nil, err
}
// 打印 body 确认 JSON 返回正确
fmt.Println("Raw JSON response:", string(body))
t := new(Translation)
err = json.Unmarshal(body, &t) // 反序列化
if err != nil {
log.Fatal("error unmarshalling JSON", err)
return nil, err
}
return t, nil
}
func main() {
// ...
translation, _ := input.TranslateString()
fmt.Println(translation) // 输出: &{[]}
}当运行上述代码时,fmt.Println(translation) 的输出为 &{[]},这表明 Translation 结构体被初始化了,但其内部字段并未成功填充。虽然原始JSON数据被正确接收并打印,但反序列化过程并未按预期工作。
问题的核心在于Go结构体 Translation 的定义与实际JSON响应的嵌套结构不匹配。
观察JSON结构:
再看我们错误的 Translation 结构体:
type Translation struct {
Data string // 错误:JSON中 data 是一个对象,而不是字符串
Translations []struct { // 错误:Translations 应该在 Data 对象内部
TranslatedText string
SourceLanguage string // 字段名与JSON键不匹配
}
}要正确解析上述JSON,Translation 结构体必须精确地反映JSON的嵌套层级和字段名。
正确的 Translation 结构体定义如下:
type Translation struct {
Data struct { // Data 字段现在是一个匿名结构体,对应JSON中的 "data" 对象
Translations []struct { // Translations 字段现在是 Data 结构体的成员
TranslatedText string `json:"translatedText"` // 使用 json tag 明确映射
DetectedSourceLanguage string `json:"detectedSourceLanguage"` // 使用 json tag 明确映射
} `json:"translations"` // 使用 json tag 明确映射
} `json:"data"` // 使用 json tag 明确映射
}在这个修正后的结构体中:
将正确的结构体定义整合到完整的程序中:
package main
import (
"encoding/json"
"fmt"
"io"
"log"
"net/http"
"net/url"
)
// 定义API密钥和API端点 (实际项目中应从配置或环境变量中获取)
const (
API_KEY = "YOUR_GOOGLE_TRANSLATE_API_KEY" // 替换为你的API密钥
API_URL = "https://translation.googleapis.com/language/translate/v2"
)
// Translation 结构体,用于反序列化Google翻译API的响应
// 结构体定义精确匹配JSON的嵌套结构和字段名
type Translation struct {
Data struct {
Translations []struct {
TranslatedText string `json:"translatedText"`
DetectedSourceLanguage string `json:"detectedSourceLanguage"`
} `json:"translations"`
} `json:"data"`
}
// InputText 结构体,用于封装翻译请求的输入
type InputText struct {
PlainText string
TargetLanguage string
Values url.Values
}
// TranslateString 方法向Google翻译API发送请求并反序列化响应
func (i *InputText) TranslateString() (*Translation, error) {
if len(i.PlainText) == 0 {
return nil, fmt.Errorf("no text specified for translation")
}
if len(i.TargetLanguage) == 0 {
return nil, fmt.Errorf("no target language specified")
}
if API_KEY == "YOUR_GOOGLE_TRANSLATE_API_KEY" || API_KEY == "" {
return nil, fmt.Errorf("API_KEY is not set or is default. Please replace with your actual key")
}
i.Values = make(url.Values)
var v = i.Values
v.Set("target", i.TargetLanguage)
v.Set("key", API_KEY)
v.Set("q", i.PlainText)
u := fmt.Sprintf("%s?%s", API_URL, v.Encode())
getResp, err := http.Get(u)
if err != nil {
return nil, fmt.Errorf("http GET request failed: %w", err)
}
defer getResp.Body.Close()
if getResp.StatusCode != http.StatusOK {
bodyBytes, _ := io.ReadAll(getResp.Body)
return nil, fmt.Errorf("API returned non-OK status: %d, response: %s", getResp.StatusCode, string(bodyBytes))
}
body, err := io.ReadAll(getResp.Body)
if err != nil {
return nil, fmt.Errorf("error reading response body: %w", err)
}
fmt.Println("--- Raw JSON Response ---")
fmt.Println(string(body))
fmt.Println("-------------------------")
t := new(Translation)
err = json.Unmarshal(body, &t)
if err != nil {
return nil, fmt.Errorf("error unmarshalling JSON: %w", err)
}
return t, nil
}
func main() {
// 示例用法
input := &InputText{
PlainText: "My name is John, I was born in Nairobi and I am 31 years old",
TargetLanguage: "es", // Spanish
Values: nil,
}
translation, err := input.TranslateString()
if err != nil {
log.Fatalf("Translation failed: %v", err)
}
fmt.Println("\n--- Parsed Translation Result ---")
if len(translation.Data.Translations) > 0 {
fmt.Printf("Translated Text: %s\n", translation.Data.Translations[0].TranslatedText)
fmt.Printf("Detected Source Language: %s\n", translation.Data.Translations[0].DetectedSourceLanguage)
} else {
fmt.Println("No translations found.")
}
fmt.Println("---------------------------------")
}
运行上述代码并替换 API_KEY 后,你将看到正确的解析结果:
--- Raw JSON Response ---
{
"data": {
"translations": [
{
"translatedText": "Mi nombre es John, nací en Nairobi y tengo 31 años.",
"detectedSourceLanguage": "en"
}
]
}
}
-------------------------
--- Parsed Translation Result ---
Translated Text: Mi nombre es John, nací en Nairobi y tengo 31 años.
Detected Source Language: en
---------------------------------在Go语言中进行JSON反序列化时,最常见的陷阱之一就是Go结构体定义与实际JSON数据结构不匹配。特别是对于嵌套的JSON对象,必须通过定义嵌套的Go结构体来精确反映这种层次关系。通过本教程的案例分析和代码示例,我们强调了正确设计结构体的重要性,并提供了一系列最佳实践,以帮助开发者更有效地处理JSON数据,避免因结构体定义错误导致的反序列化失败。
以上就是Go语言中JSON反序列化常见陷阱与嵌套结构体设计的详细内容,更多请关注php中文网其它相关文章!
每个人都需要一台速度更快、更稳定的 PC。随着时间的推移,垃圾文件、旧注册表数据和不必要的后台进程会占用资源并降低性能。幸运的是,许多工具可以让 Windows 保持平稳运行。
Copyright 2014-2025 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号