Using generics to Parse Json into Struct

I am doing a Grading Software for Practice. I am saving the Grades to a Json file and I want to try and use Generics to reduce the amount of code. I am fairly knew at Go and will programming overall.

Here is the json

{
“homework”: [
{
“Name”: “Homework1”,
“Grade”: 45
},
{
“Name”: “Homework2”,
“Grade”: 45
}
],
“lab”: [
{
“Name”: “Lab1”,
“Grade”: 12
},
{
“Name”: “Lab2”,
“Grade”: 45
}
]
}

My idea is something below but I know Go doesnt support macro substitutions
Here are the structs
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
}

Function is below
//Instead of using category2.Homework would want something like category2.result
//this way I can use the same code for all of the similar structs

func displayT 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)
}

}

Hi @huntepr1,

Yes, I can assist you with that!

To begin, in your ‘Subject’ struct, you can use a generic type parameter to represent the various categories (Homework, Lab, etc.). Here’s an illustration:

type Subject<T> struct {
    Results []T `json:"results"`
}

We’ve replaced the ‘Homework’ and ‘Lab’ elements in this version of the ‘Subject’ struct with a general ‘Results’ field that can carry any type of outcome. The ‘Results’ field is annotated with the 'json:‘results’ tag, which instructs the Go JSON package to populate this field with the ‘‘results’’ key from the JSON file.

Then, as before, define the structs ‘Homework’ and ‘Lab’:

type Homework struct {
    Name  string `json:"name"`
    Grade int    `json:"grade"`
}

type Lab struct {
    Name  string `json:"name"`
    Grade int    `json:"grade"`
}

We’ve also added ‘json’ tags to the fields of these structs. This is necessary to ensure that the Go JSON package can parse the JSON file appropriately.

Finally, you can have the ‘displayGrades’ function accept a generic type parameter:

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
}

This function takes a ‘filename’ parameter and returns a slice of results of generic type ‘T’. It reads the file, unmarshals the contents into a struct called ‘Subject’, and returns the ‘Results’ field.

To call this method and display the results, use the following syntax:

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)
    }
}

We call ‘displayGrades’ twice in this case, once with ‘Homework’ as the type parameter and once with ‘Lab’. We then loop through the results and print out each one’s name and grade.

I hope this was helpful! Please let me know if you have any other questions.

Have a nice day.

1 Like

Great explanation!!!
Because I am involve in learning generics in Go (although I have used in another languages) i found some things
to be clear.

  1. When define Subject struct, we have

type Subject struct {
Results T json:"results"
}

the “” syntax looks like “java-ish” and it is not supported by Go (When i tried), so it need to be
changed to:;

type Subject[T any] struct {
Results T json:"results"
}

Also, need to make some changes in json file too according this definition;

“results” : {
“homework”: [
{

}
],
“lab”:[
{

}
],
}
}

  1. In the displayGrades we found the notation

func displayGrades(filename string) (T, error) …
var subject Subject

change to

func displayGrades[T any](filename string) (T, error) …
var subject Subject[T]

Until now everything OK, but when i run this, and I have this code:

jsonFile := “…/grades.json”
homeworks, err := displayGradesHomework
if err != nil {
log.Fatal("Reading Homework : ", err)
}

Go reports this error :
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

It seems to me I need to write my own unmarsheller but not sure about it

Ant hint ?

Why are you using generics here? Homework and Labs are both technically grade lines, right? Why not represent them both as the same type? Something like this:

type GradeLine struct {
	Name  string `json:"Name"`
	Grade int    `json:"Grade"`
}

func main() {
	// Declare our result as a map of strings where the value is an array of GradeLines.
	var result map[string][]GradeLine
	json.Unmarshal([]byte(myData), &result)
	// Iterate over each key, printing the key and any grades under that key.
	for key, value := range result {
		fmt.Println("Grades in", key)
		for _, grade := range value {
			fmt.Println(grade.Name, ":", grade.Grade)
		}
	}
}

Assume myData is a json string. Given the JSON you posted above, this prints:

Grades in homework
Homework1 : 45
Homework2 : 45
Grades in lab
Lab1 : 12
Lab2 : 45

There’s a high probability that I’m misunderstanding what you’re trying to do here. You can run this in the go playground with this link:

You could also represent this like so:

type GradeLine struct {
	Name  string `json:"Name"`
	Grade int    `json:"Grade"`
}
type Grades struct {
	Homework []GradeLine `json:"homework"`
	Lab      []GradeLine `json:"lab"`
}

Again I’m a tad confused about what you’re trying to use generics to accomplish, since the types being represented are the same as far as I can see. I think what you are maybe looking for is parsing JSON in a more generic/flexible way (as my example above).

2 Likes

Thanks Dean, and yes, I agree with you this problem can be solved easily without generics and I am just curious about the error message. Also I just follow Hunter’s original post.
Thanks a lot!!!

Thanks for the reply. I am new to go and I have programmed on and off if my job required it. My background is SQL.

This topic was automatically closed 90 days after the last reply. New replies are no longer allowed.