I have 2 package as model:
class:
package class
import (
"encoding/json"
"student_management/model/base"
)
type Classes struct {
Classes []Class
}
type Class struct {
Id int `json:"student_id"`
Name int `json:"name"`
HomeroomTeacherId int `json:"homeroom_teacher_id"`
}
func ReadData() (chan Class, int) {
var classes Classes
byteValue := base.ReadJSON("db/student_class.json")
json.Unmarshal(byteValue, &classes)
classChannel := make(chan Class)
go func () {
for i := 0; i < len(classes.Classes); i {
classChannel <- classes.Classes[i]
}
close(classChannel)
}()
return classChannel, len(classes.Classes)
}
teacher:
package teacher
import (
"encoding/json"
base "student_management/model/base"
)
type Teachers struct {
Teachers []Teacher `json:"teachers"`
}
type Teacher struct {
base.Persions
HomeroomTeacher bool `json:"homeroom_teacher"`
}
func ReadData() (chan Teacher, int) {
var teachers Teachers
byteValue := base.ReadJSON("db/teachers.json")
json.Unmarshal(byteValue, &teachers)
teacherChannel := make(chan Teacher)
go func () {
for i := 0; i < len(teachers.Teachers); i {
teacherChannel <- teachers.Teachers[i]
}
close(teacherChannel)
}()
return teacherChannel, len(teachers.Teachers)
}
So you can see the ReadData function being repeated. And now I can use class.ReadData()
and teacher.ReadData()
to call data from channel.
How can I write ReadData() function once for both packages to use?
I tried creating a base package use generics like this:
package base
func ReadData[Models any, Model any](fileName string, m Models) (chan interface{}, int) {
byteValue := ReadJSON(fileName)
json.Unmarshal(byteValue, &m)
channel := make(chan Model)
go func () {
for i := 0; i < len(m.Models); i {
channel <- m.Models[i]
}
close(channel)
}()
return channel, len(models.Models)
}
but m.Models
not found, i mean teachers.Teachers
or classes.Classes
can not be used
Please tell me what to do in this case
CodePudding user response:
Use generics (introduced in Go 1.18). Create a single ReadData()
function, use a parameter type denoting the values you want to decode from JSON and deliver on the channel.
Note: you should check for errors and report them (including from base.ReadJSON()
).
func ReadData[T any](fileName, fieldName string) (chan T, int, error) {
var m map[string][]T
byteValue := base.ReadJSON(fileName)
if err := json.Unmarshal(byteValue, &wrapper); err != nil {
return nil, 0, err
}
values := m[fieldName]
valuesChannel := make(chan T)
go func() {
for _, v := range values {
valuesChannel <- v
}
close(valuesChannel)
}()
return valuesChannel, len(values), nil
}
Example calling it:
ch, n, err := ReadData[class.Class]("db/student_class.json", "classes")
// or
ch, n, err := ReadData[teacher.Teacher]("db/teachers.json", "teachers")
Note that it should be redundant to return the number of read values. Since you properly close the returned channel, the caller can use a for range
over the returned channel which will receive all values sent on it properly, and then terminate.
Also note that since all values are ready (decoded) when you return the channel, this concurrency is redundant and just makes things less efficient. You have a slice of the decoded values, just return it and let the caller choose how it wishes to process it (concurrently or not).
So your ReadData()
should look like this:
func ReadData[T any](fileName, fieldName string) ([]T, error) {
var m map[string][]T
byteValue := base.ReadJSON(fileName)
if err := json.Unmarshal(byteValue, &wrapper); err != nil {
return nil, err
}
return m[fieldName]
}
Also note that if the input JSON object has a single field, it's not necessary to pass the fieldName
, you can get the value from the decoded m
map like this:
func ReadData[T any](fileName string) ([]T, error) {
var m map[string][]T
byteValue := base.ReadJSON(fileName)
if err := json.Unmarshal(byteValue, &wrapper); err != nil {
return nil, err
}
for _, v := range m {
return v, nil
}
return nil, errors.New("empty JSON")
}
And then calling it is simply:
classes, err := ReadData[class.Class]("db/student_class.json")
// or
teachers, err := ReadData[teacher.Teacher]("db/teachers.json")