Home > Net >  How to common function of package model in golang?
How to common function of package model in golang?

Time:12-19

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")
  • Related