Golang Struct 拷贝/属性拷贝,类似 BeanUtil.copyProperties 的方法

yufei       1 年, 9 月 前       965

懒人教程: 一句话:用反射,至少保证不报错

代码如下

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类方法:

  1. 序列化后反序列化

    /*
    什么都好,就是没法解决字段类型不一致的问题,所以做项目的时候声明字段的一致性才是最重要的
    只要有一个字段类型不一致,就没法玩
    */
    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
    }
    

    什么都好,就是没法解决字段类型不一致的问题,只要有一个字段类型不一致,就没法玩。所以做项目的时候声明字段的一致性才是最重要的。

    相同名称的字段类型必须一样 是写程序的基本操守

  2. 反射

    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 主要是兼容所有的 JavaGoPHP.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)
        }
    }
}
目前尚无回复
简单教程 = 简单教程,简单编程
简单教程 是一个关于技术和学习的地方
现在注册
已注册用户请 登入
关于   |   FAQ   |   我们的愿景   |   广告投放   |  博客

  简单教程,简单编程 - IT 入门首选站

Copyright © 2013-2022 简单教程 twle.cn All Rights Reserved.