如何为User与Worker结构体实现通用JSON读写函数?
解决Go中重复结构体逻辑与interface{}字段访问问题
针对你遇到的User和Worker结构体重复代码、interface{}无法直接访问字段的问题,这里提供几种实用的解决方案:
1. 提取通用接口统一行为
把两个结构体的共同方法抽象成接口,让结构体各自实现接口后,就可以用接口类型编写通用逻辑,避免直接使用interface{}。
步骤示例:
首先定义通用接口:
// 认证行为接口,包含Check、SignIn方法 type Authenticatable interface { Check() error SignIn() (string, error) } // JSON加载接口,定义读取JSON文件的行为 type JSONLoader interface { LoadFromJSON(filename string) error }
然后让User和Worker实现这些接口:
// user.go中的实现 type User struct { Email string `json:"email"` Password string `json:"password"` // 其他User专属字段 } func (u *User) Check() error { // 原有的User校验逻辑 if u.Email == "" || u.Password == "" { return errors.New("用户邮箱或密码不能为空") } return nil } func (u *User) SignIn() (string, error) { if err := u.Check(); err != nil { return "", err } return "user-token-xxx", nil // 生成用户登录token的逻辑 } func (u *User) LoadFromJSON(filename string) error { data, err := os.ReadFile(filename) if err != nil { return err } return json.Unmarshal(data, u) }
// worker.go中的实现 type Worker struct { Email string `json:"email"` Password string `json:"password"` ID int `json:"id"` // 其他Worker专属字段 } func (w *Worker) Check() error { // Worker的校验逻辑,可额外检查专属字段 if w.Email == "" || w.Password == "" || w.ID <= 0 { return errors.New("Worker信息不合法") } return nil } func (w *Worker) SignIn() (string, error) { if err := w.Check(); err != nil { return "", err } return "worker-token-xxx", nil // 生成Worker登录token的逻辑 } func (w *Worker) LoadFromJSON(filename string) error { data, err := os.ReadFile(filename) if err != nil { return err } return json.Unmarshal(data, w) }
最后编写通用函数:
// 通用登录函数 func GenericSignIn(a Authenticatable) (string, error) { return a.SignIn() } // 通用JSON加载函数 func GenericLoadFromJSON(l JSONLoader, filename string) error { return l.LoadFromJSON(filename) }
调用时直接传入结构体实例即可,不需要再用interface{},自然能正常访问方法和字段。
2. 用Go泛型实现通用逻辑(Go 1.18+)
如果两个结构体有共同字段(比如Email、Password),可以通过泛型约束来直接访问这些字段,同时复用JSON读取等通用逻辑。
步骤示例:
定义包含共同字段的泛型约束:
// 约束类型,要求类型是包含Email和Password字段的结构体 type AuthEntity interface { ~struct { Email string Password string // 可添加其他共同字段 } }
编写通用校验和JSON读取函数:
// 通用校验函数,直接访问约束中的字段 func GenericCheck[T AuthEntity](entity T) error { if entity.Email == "" || entity.Password == "" { return errors.New("邮箱或密码不能为空") } return nil } // 通用JSON读取函数,支持任意结构体类型 func GenericReadJSON[T any](filename string) (T, error) { var result T data, err := os.ReadFile(filename) if err != nil { return result, err } err = json.Unmarshal(data, &result) return result, err }
调用示例:
// 读取用户列表 users, err := GenericReadJSON[[]User]("users.json") if err != nil { // 处理错误 } for _, u := range users { if err := GenericCheck(u); err != nil { // 处理校验错误 } } // 读取Worker列表 workers, err := GenericReadJSON[[]Worker]("workers.json")
3. 结构体嵌入复用基础逻辑
如果User和Worker有大量共同字段和逻辑,可以把这部分抽成基础结构体,通过嵌入的方式复用代码。
步骤示例:
定义基础Auth结构体,包含共同字段和逻辑:
// auth.go type Auth struct { Email string `json:"email"` Password string `json:"password"` } func (a *Auth) Check() error { if a.Email == "" || a.Password == "" { return errors.New("邮箱或密码不能为空") } return nil } func (a *Auth) GenerateBaseToken() (string, error) { if err := a.Check(); err != nil { return "", err } return fmt.Sprintf("base-token-%s", a.Email), nil }
在User和Worker中嵌入Auth结构体:
// user.go type User struct { Auth // 嵌入基础Auth结构体 Username string `json:"username"` // User专属字段 } // 重写SignIn方法,添加User专属逻辑 func (u *User) SignIn() (string, error) { baseToken, err := u.GenerateBaseToken() if err != nil { return "", err } return fmt.Sprintf("user-%s", baseToken), nil }
// worker.go type Worker struct { Auth // 嵌入基础Auth结构体 ID int `json:"id"` // Worker专属字段 } // 重写Check方法,添加Worker专属校验 func (w *Worker) Check() error { // 先调用基础Auth的校验 if err := w.Auth.Check(); err != nil { return err } // 检查Worker专属字段 if w.ID <= 0 { return errors.New("Worker ID不合法") } return nil } func (w *Worker) SignIn() (string, error) { baseToken, err := w.GenerateBaseToken() if err != nil { return "", err } return fmt.Sprintf("worker-%d-%s", w.ID, baseToken), nil }
这种方式既复用了基础逻辑,又能保留各自的专属字段和自定义方法,同时JSON序列化也会自动包含嵌入的字段。
内容的提问来源于stack exchange,提问作者Oshank Kashyap
相关产品推荐
相关产品推荐

