You need to enable JavaScript to run this app.
最新活动
大模型
产品
解决方案
定价
生态与合作
支持与服务
开发者
了解我们

如何在Golang顶层结构体中获取嵌套JSON值?

如何将嵌套JSON映射到Go语言的扁平结构体

你遇到的是Go语言JSON解析里的结构体扁平化映射问题——不想用多层嵌套结构体,直接把深层的fields.description映射到顶层的Description字段,对吧?完全可行!下面给你两种实用的解决方法:


方法一:自定义UnmarshalJSON实现自定义解析

这种方法最灵活,直接在目标结构体上实现json.Unmarshaler接口,自己控制解析逻辑:

package main

import (
	"encoding/json"
	"fmt"
)

// 你的目标扁平结构体
type Todo struct {
	Number string `json:"number"`
	Issues []Issue `json:"issues"`
}

type Issue struct {
	Key         string `json:"key"`
	Self        string `json:"self"`
	Description string // 不需要json标签,我们自己处理映射
}

// 为Todo实现自定义的UnmarshalJSON方法
func (t *Todo) UnmarshalJSON(data []byte) error {
	// 定义中间嵌套结构体,完全匹配原始JSON的结构
	type RawTodo struct {
		Ticket struct {
			Number string `json:"number"`
			Issues []struct {
				Key    string `json:"key"`
				Self   string `json:"self"`
				Fields struct {
					Description string `json:"description"`
				} `json:"fields"`
			} `json:"issues"`
		} `json:"ticket"`
	}

	var raw RawTodo
	if err := json.Unmarshal(data, &raw); err != nil {
		return err
	}

	// 将中间结构体的数据映射到目标扁平结构体
	t.Number = raw.Ticket.Number
	t.Issues = make([]Issue, len(raw.Ticket.Issues))
	for i, rawIssue := range raw.Ticket.Issues {
		t.Issues[i] = Issue{
			Key:         rawIssue.Key,
			Self:        rawIssue.Self,
			Description: rawIssue.Fields.Description,
		}
	}
	return nil
}

func main() {
	rawJSON := `{ "ticket": { "number": "2", "issues": [ { "key": "TA-2", "self": "http://localhost:8080/rest/api/2/issue/10100", "fields": { "description": "This template is used to create openshift project.\n|Type|Value|Help|\n|project_name|personal_project|Enter the name of your openshift project|\n|memory|8GB|Enter the desired amount of memory|" } }, { "key": "TA-1", "self": "http://localhost:8080/rest/api/2/issue/10000", "fields": { "description": "This template is used to create openshift project.\n|Type|Value|Help|\n|project_name|my_openshift_project|Enter the name of your openshift project|\n|memory|4GB|Enter the desired amount of memory|" } } ] } }`

	var todo Todo
	err := json.Unmarshal([]byte(rawJSON), &todo)
	if err != nil {
		fmt.Println("解析错误:", err)
		return
	}

	// 验证结果,输出扁平化后的JSON
	result, _ := json.MarshalIndent(todo, "", "  ")
	fmt.Println(string(result))
}

这种方法的核心是用中间结构体匹配原始JSON的嵌套结构,解析完成后再手动把数据映射到你想要的扁平结构体中。好处是完全按照你的需求控制字段映射,不受JSON标签的限制。


方法二:先解析到临时嵌套结构体,再转换

如果不想自定义Unmarshal方法,也可以分两步走:先把JSON解析到一个和原始结构完全一致的临时结构体,再手动转换成目标扁平结构体:

package main

import (
	"encoding/json"
	"fmt"
)

// 目标扁平结构体
type Todo struct {
	Number string `json:"number"`
	Issues []Issue `json:"issues"`
}

type Issue struct {
	Key         string `json:"key"`
	Self        string `json:"self"`
	Description string `json:"description"`
}

// 临时嵌套结构体,完全匹配原始JSON的结构
type RawResponse struct {
	Ticket struct {
		Number string `json:"number"`
		Issues []struct {
			Key    string `json:"key"`
			Self   string `json:"self"`
			Fields struct {
				Description string `json:"description"`
			} `json:"fields"`
		} `json:"issues"`
	} `json:"ticket"`
}

func main() {
	rawJSON := `{ "ticket": { "number": "2", "issues": [ { "key": "TA-2", "self": "http://localhost:8080/rest/api/2/issue/10100", "fields": { "description": "This template is used to create openshift project.\n|Type|Value|Help|\n|project_name|personal_project|Enter the name of your openshift project|\n|memory|8GB|Enter the desired amount of memory|" } }, { "key": "TA-1", "self": "http://localhost:8080/rest/api/2/issue/10000", "fields": { "description": "This template is used to create openshift project.\n|Type|Value|Help|\n|project_name|my_openshift_project|Enter the name of your openshift project|\n|memory|4GB|Enter the desired amount of memory|" } } ] } }`

	var raw RawResponse
	err := json.Unmarshal([]byte(rawJSON), &raw)
	if err != nil {
		fmt.Println("解析错误:", err)
		return
	}

	// 转换为目标扁平结构体
	todo := Todo{
		Number: raw.Ticket.Number,
		Issues: make([]Issue, len(raw.Ticket.Issues)),
	}
	for i, item := range raw.Ticket.Issues {
		todo.Issues[i] = Issue{
			Key:         item.Key,
			Self:        item.Self,
			Description: item.Fields.Description,
		}
	}

	// 输出结果
	result, _ := json.MarshalIndent(todo, "", "  ")
	fmt.Println(string(result))
}

这种方法更直观,适合简单的场景。先完成原始JSON的解析,再做一次数据拷贝,把深层字段提取到顶层结构体中。


为什么你的原始写法无效?

Go标准库的encoding/json并不支持用json:"field.description"这种点符号来映射深层嵌套字段——它只能直接匹配当前层级的键名,所以这种写法是行不通的,必须通过上面的两种方法来处理。

内容的提问来源于stack exchange,提问作者Johann

火山引擎 最新活动