懒人教程: 一句话:用反射,至少保证不报错
代码如下
func CopyProperties2(dst, src interface{}) { sval := reflect.ValueOf(src).Elem() dval := reflect.ValueOf(dst).Elem() for i := 0; i < sval.NumField(); i++ { value := sval.Field(i) name := sval.Type().Field(i).Name dvalue := dval.FieldByName(name) if !dvalue.IsValid() { continue } stype := uint(sval.Type().Field(i).Type.Kind()) dtype := uint(dval.Type().Field(i).Type.Kind()) fmt.Println(stype, "->", dtype) if stype == dtype { dvalue.Set(value) } } }
详细教程
使用 Go 做后端相关应用的时候,总是免不了在多个相类似的结构体struct 的相同属性之间来回赋值,这种重复性的工作做久了也是觉得烦死人了,比如下面这个用 gin
写的例子
/* 请求表单 */ type TopicForm struct { Title string `json:"title"` Content string `json:"content"` } /* 数据库实体 这里加 json 主要方便日志 */ type TopicModel struct { Title string `json:"title"` Content string `json:"content"` CreatedAt int64 `json:"created_at"` UpdatedAt int64 `json:"updated_at"` DeletedAt int64 `json:"-" gorm:"index"` Views int `json:"views" gorm:"views"` } /* web response */ type TopicResponse struct { Title string `json:"title"` Content string `json:"content"` Author string `json:"author"` Views string `json:"views"` CreatedAt time.Time `json:"created_at"` UpdatedAt time.Time `json:"updated_at"` }
上面的三个接口,每个接口字段都有所不同,但大部分都一样
// 为啥用 micro 主要是兼容所有的 Java、Go、PHP、.NET 时间戳格式 now := time.Now().UnixMicro() req := &TopicForm{ Title: "简单教程不简单", Content: "简单教程贼简单啦", } topic := &TopicModel{ Title: req.Title, Content: req.Content, CreatedAt: now, UpdatedAt: now, Views: 12345, // 初见则为 1 } res := &TopicResponse{ Title: topic.Title, Content: topic.Content, UpdatedAt: time.UnixMicro(topic.UpdatedAt), CreatedAt: time.UnixMicro(topic.CreatedAt), Author: "书童", Views: strconv.Itoa(topic.Views), // 初见则为 1 }
三个结构体之间赋值,结构体成员不多的时候我们还可以一个一个拷贝,但是多了,就有点难受了
为了解决这个问题,有2类方法:
-
序列化后反序列化
/* 什么都好,就是没法解决字段类型不一致的问题,所以做项目的时候声明字段的一致性才是最重要的 只要有一个字段类型不一致,就没法玩 */ func CopyProperties(dst, src interface{}) (err error) { aj, err := json.Marshal(src) if err != nil { return err } err = json.Unmarshal(aj, &dst) if err != nil { return err } return nil }
什么都好,就是没法解决字段类型不一致的问题,只要有一个字段类型不一致,就没法玩。所以做项目的时候声明字段的一致性才是最重要的。
相同名称的字段类型必须一样 是写程序的基本操守
-
反射
func CopyProperties2(dst, src interface{}) { sval := reflect.ValueOf(src).Elem() dval := reflect.ValueOf(dst).Elem() for i := 0; i < sval.NumField(); i++ { value := sval.Field(i) name := sval.Type().Field(i).Name dvalue := dval.FieldByName(name) if !dvalue.IsValid() { continue } stype := uint(sval.Type().Field(i).Type.Kind()) dtype := uint(dval.Type().Field(i).Type.Kind()) fmt.Println(stype, "->", dtype) if stype == dtype { dvalue.Set(value) } } }
类型不一样我们可以不赋值,但至少不会报错,所以用哪个,没得选,只能用反射
至于不一样的类型,那就自己赋值咯
其实还可以根据不同的类型自己做判断,但是复杂度太高了,我觉得没必要啊
完整的源代码
我自己做实验的源代码如下
package main import ( "encoding/json" "fmt" "reflect" "strconv" "time" ) /* 请求表单 */ type TopicForm struct { Title string `json:"title"` Content string `json:"content"` } /* 数据库实体 这里加 json 主要方便日志 */ type TopicModel struct { Title string `json:"title"` Content string `json:"content"` CreatedAt int64 `json:"created_at"` UpdatedAt int64 `json:"updated_at"` DeletedAt int64 `json:"-" gorm:"index"` Views int `json:"views" gorm:"views"` } /* web response */ type TopicResponse struct { Title string `json:"title"` Content string `json:"content"` Author string `json:"author"` Views string `json:"views"` CreatedAt time.Time `json:"created_at"` UpdatedAt time.Time `json:"updated_at"` } func main() { // 为啥用 micro 主要是兼容所有的 Java、Go、PHP、.NET 时间戳格式 now := time.Now().UnixMicro() req := &TopicForm{ Title: "简单教程不简单", Content: "简单教程贼简单啦", } topic := &TopicModel{ Title: req.Title, Content: req.Content, CreatedAt: now, UpdatedAt: now, Views: 12345, // 初见则为 1 } res := &TopicResponse{ Title: topic.Title, Content: topic.Content, UpdatedAt: time.UnixMicro(topic.UpdatedAt), CreatedAt: time.UnixMicro(topic.CreatedAt), Author: "书童", Views: strconv.Itoa(topic.Views), // 初见则为 1 } fmt.Printf("%+v\n", req) fmt.Printf("%+v\n", topic) fmt.Printf("%+v\n", res) fmt.Println("---------------------") // 使用 json 方法 // 打开下面的注释看情况 /* var res2 TopicResponse err := CopyProperties(&res2, topic) if err != nil { panic(err) } fmt.Printf("%+v\n", res2) */ fmt.Println("---------------------") // 使用反射方法 var res3 TopicResponse CopyProperties2(&res3, topic) fmt.Printf("%+v\n", res3) } /* 什么都好,就是没法解决字段类型不一致的问题,所以做项目的时候声明字段的一致性才是最重要的 只要有一个字段类型不一致,就没法玩 */ func CopyProperties(dst, src interface{}) (err error) { aj, err := json.Marshal(src) if err != nil { return err } err = json.Unmarshal(aj, &dst) if err != nil { return err } return nil } func CopyProperties2(dst, src interface{}) { sval := reflect.ValueOf(src).Elem() dval := reflect.ValueOf(dst).Elem() for i := 0; i < sval.NumField(); i++ { value := sval.Field(i) name := sval.Type().Field(i).Name dvalue := dval.FieldByName(name) if !dvalue.IsValid() { continue } stype := uint(sval.Type().Field(i).Type.Kind()) dtype := uint(dval.Type().Field(i).Type.Kind()) fmt.Println(stype, "->", dtype) if stype == dtype { dvalue.Set(value) } } }
目前尚无回复