golang 对不同结构体中数据进行相互转换的几种常用方法

常用的不同结构体中的数据相互转换的方法

1. 利用json包的marshal和unmarshal

要求:json标签的值必须一致
示例:

package main
import (
	"encoding/json"
	"fmt"
)
type A struct {
	Name string `json:"name"`
	Age int `json:"age"`
	Gender string `json:"gender"`
}
type B struct {
	Name string `json:"name"`
	Age int `json:"age"`
	Weight string `json:"weight"`
}

func main() {
	a:=A{
		Name:   "小可",
		Age:    121,
		Gender: "男",
	}
	var b B
	jsonBytes,_:=json.Marshal(a)
	err:=json.Unmarshal(jsonBytes,&b)
	if err!=nil{
		fmt.Println(err.Error())
	}else{
		fmt.Printf("%+v",b)
	}
}

输出:

{Name:小可 Age:121 Weight:}

2. 使用第三方包 copier 进行数据转换

要求:结构体的数据结构和字段名必须一致

go get github.com/jinzhu/copier

使用示例

import (
	"fmt"
	"github.com/jinzhu/copier"
)

func main() {
	a:=A{
		Name:   "小可",
		Age:    121,
		Gender: "男",
	}
	var b B
	err := copier.Copy(&a, &b)
	if err!=nil{
		fmt.Println(err.Error())
	}else{
		fmt.Printf("%+v",b)
	}
}

对字段名相同,但是数据类型不同的字段,可以像这样额外添加option去转换

// time.Time 和 string 之间相互转换的方法
var CopierProtoOptions = copier.Option{
	IgnoreEmpty: true,
	DeepCopy:    true,
	Converters: []copier.TypeConverter{
		{
			SrcType: time.Time{},
			DstType: copier.String,
			Fn: func(src interface{}) (interface{}, error) {
				s, ok := src.(time.Time)
				if !ok {
					return nil, errors.New("src type :time.Time not matching")
				}
				return s.Format("2006-01-02 15:04:05"), nil
			},
		},
		{
			SrcType: copier.String,
			DstType: time.Time{},
			Fn: func(src interface{}) (interface{}, error) {
				s, ok := src.(string)
				if !ok {
					return nil, errors.New("src type :time.Time not matching")
				}
				tt, err := time.ParseInLocation(s, "2006-01-02 15:04:05", shanghai)
				return tt, err
			},
		},
	},
}

使用

type A struct {
	Name string 
	Age int
	Gender string 
	BirthDay string 
}
type B struct {
	Name string 
	Age int
	Weight string
	BirthDay time.time
}

func main() {
	a:=A{
		Name:   "小可",
		Age:    121,
		Gender: "男",
		BirthDay :"1997-05-08 11:20:11",
	}
	var b B
	err := copier.CopyWithOption(&a, &b,CopierProtoOptions)
	if err!=nil{
		fmt.Println(err.Error())
	}else{
		fmt.Printf("%+v",b)
	}
}

3.对结构不同的结构体进行转换

举例有如下结构体,需要把DataRequest中的数据转换到ProtoRequest 中。特点是,ProtoRequest 中,定义了一个Query字段来继承*CommonQuery类型。

type CommonQuery struct {
	Id   int
	Name string
}
type DataRequest struct {
	CommonQuery
	Page     int64 `json:"page"`
	PageSize int64 `json:"pageSize"`
}
type ProtoRequest struct {
	Query    *CommonQuery `json:"query"`
	Page     int64        `json:"page"`
	PageSize int64        `json:"pageSize"`
}

此时需要自定义一个方法,示例如下,大家可以参考下面的代码进行适当修改。

var shanghai, _ = time.LoadLocation("Asia/Shanghai")
func ConvertData(from interface{}, to interface{}) {
	var proxyField = "Query"
	fromValue := reflect.ValueOf(from)
	toValue := reflect.ValueOf(to)
	toType := reflect.TypeOf(to)

	// 获取From结构体的字段信息
	fromType := fromValue.Type().Elem()
	for i := 0; i < fromType.NumField(); i++ {
		// 获取字段名和字段值
		fieldName := fromType.Field(i).Name
		fieldValue := fromValue.Elem().FieldByName(fieldName)
		if fieldName != proxyField {
			_, exists := toType.Elem().FieldByName(fieldName)
			if exists {
				// 设置To结构体中相应字段的值
				toValue.Elem().FieldByName(fieldName).Set(fieldValue)
			}
		}
	}
	queryField, exists := toType.Elem().FieldByName(proxyField)
	if exists {
		var queryFieldTypeName string
		// 指针类型额外处理,拿到真实的数据类型
		if queryField.Type.Kind() == reflect.Ptr {
			queryFieldTypeName = queryField.Type.Elem().String()
		} else {
			queryFieldTypeName = queryField.Type.Kind().String()
		}
		//处理拿到的结构体类型如 utils.xxxx的类型,去掉utils.这部分
		if strings.Contains(queryFieldTypeName, ".") {
			queryFieldTypeName = strings.Split(queryFieldTypeName, ".")[1]
		}
		fromQueryValue := fromValue.Elem().FieldByName(queryFieldTypeName)
		if fromQueryValue.IsValid() && fromQueryValue.CanAddr() {
			toValue.Elem().FieldByName(proxyField).Set(fromQueryValue.Addr())
		}
	}
}


  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值