使用泛型将JSON解析为结构体的Golang实践指南
使用泛型将JSON解析为结构体的Golang实践指南 我正在开发一个用于练习的评分软件。我将成绩保存到JSON文件中,并希望尝试使用泛型来减少代码量。我对Go语言以及整体编程还比较陌生。
以下是JSON内容:
{
"homework": [
{
"Name": "Homework1",
"Grade": 45
},
{
"Name": "Homework2",
"Grade": 45
}
],
"lab": [
{
"Name": "Lab1",
"Grade": 12
},
{
"Name": "Lab2",
"Grade": 45
}
]
}
我的想法类似于下面的代码,但我知道Go不支持宏替换。 以下是结构体定义:
type Subject struct {
Homework []Homework `json:"homework"`
Lab []Lab `json:"lab"`
//Weight []Weight
}
type Homework struct {
Name string
Grade int
}
type Lab struct {
Name string
Grade int
}
函数如下:
// 我希望使用类似 category2.result 的写法,而不是 category2.Homework
// 这样我就可以对所有类似的结构体使用相同的代码
func display<T Grades> {
var result []T
//var cat map[string]any
content, err := ioutil.ReadFile("grades.json")
if err != nil {
log.Fatal(err)
}
category2 := Subject{}
err = json.Unmarshal(content, &category2)
fmt.Println(category2)
res := category2
fmt.Println(res)
for key, value := range category2.Homework {
fmt.Println(key, value)
}
if err != nil {
log.Fatal(err)
}
}
更多关于使用泛型将JSON解析为结构体的Golang实践指南的实战教程也可以访问 https://www.itying.com/category-94-b0.html
感谢您的回复。我是Go语言的新手,如果工作需要,我会断断续续地进行编程。我的背景是SQL。
更多关于使用泛型将JSON解析为结构体的Golang实践指南的实战系列教程也可以访问 https://www.itying.com/category-94-b0.html
谢谢Dean,是的,我同意你的观点,这个问题可以很容易地不使用泛型来解决,我只是对这个错误信息感到好奇。另外,我只是遵循了Hunter的原帖。 非常感谢!!!
为什么在这里使用泛型?从技术上讲,作业和实验都是成绩记录行,对吗?为什么不将它们表示为相同的类型呢?类似这样:
type GradeLine struct {
Name string `json:"Name"`
Grade int `json:"Grade"`
}
func main() {
// 将结果声明为一个字符串映射,其值是GradeLine数组。
var result map[string][]GradeLine
json.Unmarshal([]byte(myData), &result)
// 遍历每个键,打印键和该键下的所有成绩。
for key, value := range result {
fmt.Println("Grades in", key)
for _, grade := range value {
fmt.Println(grade.Name, ":", grade.Grade)
}
}
}
假设 myData 是一个JSON字符串。根据你上面发布的JSON,这将打印:
Grades in homework
Homework1 : 45
Homework2 : 45
Grades in lab
Lab1 : 12
Lab2 : 45
我很可能误解了你在这里想要实现的目标。你可以通过此链接在Go Playground中运行这段代码:

你也可以这样表示:
type GradeLine struct {
Name string `json:"Name"`
Grade int `json:"Grade"`
}
type Grades struct {
Homework []GradeLine `json:"homework"`
Lab []GradeLine `json:"lab"`
}
我仍然对你试图使用泛型来实现的目标感到有点困惑,因为就我所见,被表示的类型是相同的。我认为你可能正在寻找一种更通用/灵活的方式来解析JSON(就像我上面的例子那样)。
非常好的解释!!! 由于我正在学习 Go 中的泛型(虽然我在其他语言中使用过),我发现了一些需要澄清的地方。
-
定义 Subject 结构体时,我们有
type Subject struct { Results []T `json:"results"` }这种
[]T语法看起来像是“Java风格”的,并且 Go 不支持(我尝试过),所以需要改为:type Subject[T any] struct { Results []T `json:"results"` }同时,根据这个定义,也需要对 JSON 文件做一些修改:
"results" : { "homework": [ { ... } ], "lab":[ { ... } ], } -
在
displayGrades函数中,我们发现了[]T的写法func displayGrades(filename string) ([]T, error) ... var subject Subject改为
func displayGrades[T any](filename string) ([]T, error) ... var subject Subject[T]到目前为止一切正常,但当我运行这个程序,并且我有以下代码时:
jsonFile := ".../grades.json" homeworks, err := displayGrades[Homework](jsonFile) if err != nil { log.Fatal("Reading Homework : ", err) } ...Go 报告了这个错误: 2023/05/07 19:10:09 Reading Homework : json: cannot unmarshal object into Go struct field Subject[main.Homework]results of type []main.Homework exit status 1
在我看来,我需要编写自己的反序列化器,但不太确定。
有什么提示吗?
是的,我可以帮助你!
首先,在你的 Subject 结构体中,可以使用泛型类型参数来表示不同的类别(如作业、实验等)。以下是一个示例:
type Subject<T> struct {
Results []T `json:"results"`
}
在这个版本的 Subject 结构体中,我们用通用的 Results 字段替换了 Homework 和 Lab 元素,该字段可以承载任何类型的结果。Results 字段使用了 json:"results" 标签进行注解,这指示 Go 的 JSON 包使用 JSON 文件中的 "results" 键来填充此字段。
然后,像之前一样定义 Homework 和 Lab 结构体:
type Homework struct {
Name string `json:"name"`
Grade int `json:"grade"`
}
type Lab struct {
Name string `json:"name"`
Grade int `json:"grade"`
}
我们还为这些结构体的字段添加了 json 标签。这是确保 Go 的 JSON 包能够正确解析 JSON 文件所必需的。
最后,你可以让 displayGrades 函数接受一个泛型类型参数:
func displayGrades<T>(filename string) ([]T, error) {
content, err := ioutil.ReadFile(filename)
if err != nil {
return nil, err
}
var subject Subject<T>
err = json.Unmarshal(content, &subject)
if err != nil {
return nil, err
}
return subject.Results, nil
}
这个函数接收一个 filename 参数,并返回一个泛型类型 T 的结果切片。它读取文件,将内容解组到一个名为 Subject 的结构体中,并返回 Results 字段。
要调用此方法并显示结果,请使用以下语法:
func main() {
homework, err := displayGrades<Homework>("grades.json")
if err != nil {
log.Fatal(err)
}
for _, hw := range homework {
fmt.Printf("Name: %s, Grade: %d\n", hw.Name, hw.Grade)
}
lab, err := displayGrades<Lab>("grades.json")
if err != nil {
log.Fatal(err)
}
for _, l := range lab {
fmt.Printf("Name: %s, Grade: %d\n", l.Name, l.Grade)
}
}
在这个例子中,我们调用了两次 displayGrades,一次使用 Homework 作为类型参数,一次使用 Lab。然后我们遍历结果并打印出每个结果的名称和成绩。
希望这对你有帮助!如果你还有其他问题,请告诉我。
祝你今天愉快。
以下是使用Go泛型解析JSON到结构体的实现方案:
package main
import (
"encoding/json"
"fmt"
"io/ioutil"
"log"
)
// 基础成绩接口
type GradeItem interface {
Homework | Lab
GetName() string
GetGrade() int
}
// 泛型成绩结构体
type GenericGrade[T GradeItem] struct {
Items []T `json:"items"`
}
// 具体类型实现
type Homework struct {
Name string `json:"Name"`
Grade int `json:"Grade"`
}
func (h Homework) GetName() string {
return h.Name
}
func (h Homework) GetGrade() int {
return h.Grade
}
type Lab struct {
Name string `json:"Name"`
Grade int `json:"Grade"`
}
func (l Lab) GetName() string {
return l.Name
}
func (l Lab) GetGrade() int {
return l.Grade
}
// 主结构体
type Subject struct {
Homework []Homework `json:"homework"`
Lab []Lab `json:"lab"`
}
// 泛型显示函数
func displayGrades[T GradeItem](items []T) {
for i, item := range items {
fmt.Printf("%d: %s - %d\n", i, item.GetName(), item.GetGrade())
}
}
// 泛型解析函数
func parseCategory[T GradeItem](jsonData []byte, fieldName string) ([]T, error) {
var subject Subject
if err := json.Unmarshal(jsonData, &subject); err != nil {
return nil, err
}
// 使用类型断言获取对应字段
switch fieldName {
case "homework":
if items, ok := any(subject.Homework).([]T); ok {
return items, nil
}
case "lab":
if items, ok := any(subject.Lab).([]T); ok {
return items, nil
}
}
return nil, fmt.Errorf("field not found or type mismatch")
}
// 更简洁的泛型版本
func loadAndDisplay[T GradeItem](filename, fieldName string) error {
content, err := ioutil.ReadFile(filename)
if err != nil {
return err
}
items, err := parseCategory[T](content, fieldName)
if err != nil {
return err
}
displayGrades(items)
return nil
}
func main() {
// 示例1:解析homework
fmt.Println("Homework Grades:")
if err := loadAndDisplay[Homework]("grades.json", "homework"); err != nil {
log.Fatal(err)
}
// 示例2:解析lab
fmt.Println("\nLab Grades:")
if err := loadAndDisplay[Lab]("grades.json", "lab"); err != nil {
log.Fatal(err)
}
// 示例3:直接使用泛型函数
content, _ := ioutil.ReadFile("grades.json")
homeworks, _ := parseCategory[Homework](content, "homework")
fmt.Println("\nDirect Homework Access:")
displayGrades(homeworks)
labs, _ := parseCategory[Lab](content, "lab")
fmt.Println("\nDirect Lab Access:")
displayGrades(labs)
}
更进一步的简化方案,使用单一泛型结构体:
// 统一成绩结构体
type Grade struct {
Name string `json:"Name"`
Grade int `json:"Grade"`
}
// 泛型容器
type GradeContainer[T any] struct {
Data []T `json:"data"`
}
// 泛型解析器
func parseJSONField[T any](jsonData []byte, fieldName string) ([]T, error) {
var raw map[string]json.RawMessage
if err := json.Unmarshal(jsonData, &raw); err != nil {
return nil, err
}
if fieldData, exists := raw[fieldName]; exists {
var items []T
if err := json.Unmarshal(fieldData, &items); err != nil {
return nil, err
}
return items, nil
}
return nil, fmt.Errorf("field %s not found", fieldName)
}
// 使用示例
func main() {
content, _ := ioutil.ReadFile("grades.json")
// 解析为Grade结构体
homeworks, _ := parseJSONField[Grade](content, "homework")
fmt.Println("Homeworks:", homeworks)
labs, _ := parseJSONField[Grade](content, "lab")
fmt.Println("Labs:", labs)
// 计算总成绩的泛型函数
total := calculateTotal[Grade](homeworks)
fmt.Printf("Homework Total: %d\n", total)
}
// 泛型计算函数
func calculateTotal[T interface{ GetGrade() int }](items []T) int {
total := 0
for _, item := range items {
total += item.GetGrade()
}
return total
}
这个实现通过Go 1.18+的泛型特性,减少了重复代码,同时保持了类型安全。

