Compare commits
9 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 3fb090a620 | |||
| 77a8322aad | |||
| e57caf3080 | |||
| 0e0f076fc4 | |||
| 64b225f26d | |||
| 898bdf7f38 | |||
| 02152b44bf | |||
| 0b7a1cf46d | |||
| becba13ed9 |
+122
@@ -0,0 +1,122 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"code.yun.ink/pkg/structx"
|
||||||
|
"github.com/shopspring/decimal"
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
|
||||||
|
// demo1()
|
||||||
|
demo2()
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func demo2() {
|
||||||
|
str := `{
|
||||||
|
"appid": "1023538",
|
||||||
|
"total_fee": "100",
|
||||||
|
"key": "fIIu2UQb6jvdkOGqIx1zvXzc4grCkjb6",
|
||||||
|
"payment": "alipay.qrcode",
|
||||||
|
"notify_url": "http://admin-v2.hk.blueoceantech.co/api/demo",
|
||||||
|
"sign": "12345"
|
||||||
|
}`
|
||||||
|
p := &PayReq{}
|
||||||
|
|
||||||
|
m := map[string]any{}
|
||||||
|
json.Unmarshal([]byte(str), &m)
|
||||||
|
|
||||||
|
ch2, err := structx.NewStructProcessor(structx.AllowUnknownFields()).AttactToStructAny(p, m)
|
||||||
|
fmt.Println(ch2, err)
|
||||||
|
fmt.Printf("p:%+v\n", p)
|
||||||
|
|
||||||
|
ch1, err := structx.AttactToStructAny(p, m)
|
||||||
|
fmt.Println(ch1, err)
|
||||||
|
fmt.Printf("p:%+v\n", p)
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
type PaymentMethod string
|
||||||
|
type Provider string
|
||||||
|
type OrderWallet string
|
||||||
|
type CoinType string
|
||||||
|
type PayReq struct {
|
||||||
|
Appid int64 `json:"appid"` // 必填 appid,由商户后台获取,或者登录获取
|
||||||
|
StoreId int64 `json:"storeid"` // 选填 storeid,指定门店交易
|
||||||
|
OutTradeNo string `json:"out_trade_no"` // 选填 商户自身订单号
|
||||||
|
Payment PaymentMethod `json:"payment"` // 必填 支付方式
|
||||||
|
H5RedirectUrl string `json:"h5_redirect_url"` // 选填 微信香港钱包公众号支付跳转url,支付宝WAP跳转ur
|
||||||
|
SpbillCreateIp string `json:"spbill_create_ip"` // 选填 客户IP 支付用户的IP,微信H5(MWEB)必传
|
||||||
|
TotalFee decimal.Decimal `json:"total_fee"` // 必填 订单金额(单位分)
|
||||||
|
Sign string `json:"sign"` // 必填 签名
|
||||||
|
Provider Provider `json:"provider"` // 选填 支付提供方:alipay wechat [可传但需过滤]
|
||||||
|
Code string `json:"code"` // 选填 支付授权码(micropay必填)
|
||||||
|
NotifyUrl string `json:"notify_url"` // 选填 异步通知url
|
||||||
|
// OpenId string `json:"open_id"` // 选填 openid 机构商户appid下的唯一标识。openid和sub_openid可以选传其中之一
|
||||||
|
SubAppid string `json:"sub_appid"` // 选填 子商户公众号的微信APPID
|
||||||
|
SubOpenid string `json:"sub_openid"` // 选填 openid 商户公众号、小程序获取的openid 用户在子商户appid下的唯一标识。openid和sub_openid可以选传其中之一
|
||||||
|
Body string `json:"body"` // 选填 商品名称(显示)
|
||||||
|
Attach string `json:"attach"` // 选填 附加数据(客户自己的)
|
||||||
|
Wallet OrderWallet `json:"wallet"` // 选填 钱包(支付宝需要)
|
||||||
|
Currency CoinType `json:"currency"` // 选填 客户的下单币种(需要根据不同平台指定默认值)
|
||||||
|
TimeoutSecond int64 `json:"timeout_second"` // 选填 客户的下单超时时间(需要根据不同平台指定默认值)
|
||||||
|
}
|
||||||
|
|
||||||
|
func demo1() {
|
||||||
|
|
||||||
|
p1 := &Person{}
|
||||||
|
updateMap1 := map[string]any{
|
||||||
|
"name": "Alice",
|
||||||
|
"age": 30,
|
||||||
|
"active": true,
|
||||||
|
}
|
||||||
|
p2 := &Person2{}
|
||||||
|
p3 := &Person2{}
|
||||||
|
p4 := &Person2{}
|
||||||
|
p5 := &struct {
|
||||||
|
Name string `json:"name"`
|
||||||
|
Age int `json:"age"`
|
||||||
|
Active bool `json:"active"`
|
||||||
|
}{}
|
||||||
|
p6 := &decimal.Decimal{}
|
||||||
|
|
||||||
|
ch1, err := structx.AttactToStructAny(p1, updateMap1)
|
||||||
|
fmt.Println(ch1, err)
|
||||||
|
fmt.Printf("%+v\n", p1)
|
||||||
|
|
||||||
|
ch, err := structx.AttactToStructAny(p2, updateMap1) // just for compile
|
||||||
|
fmt.Println(ch, err)
|
||||||
|
fmt.Printf("%+v\n", p2)
|
||||||
|
|
||||||
|
ch3, err := structx.AttactToStructAny(p3, updateMap1) // just for compile
|
||||||
|
fmt.Println(ch3, err)
|
||||||
|
fmt.Printf("%+v\n", p3)
|
||||||
|
|
||||||
|
ch4, err := structx.AttactToStructAny(p4, updateMap1) // just for compile
|
||||||
|
fmt.Println(ch4, err)
|
||||||
|
fmt.Printf("%+v\n", p4)
|
||||||
|
|
||||||
|
ch5, err := structx.AttactToStructAny(p5, updateMap1) // just for compile
|
||||||
|
fmt.Println(ch5, err)
|
||||||
|
fmt.Printf("%+v\n", p5)
|
||||||
|
|
||||||
|
ch6, err := structx.AttactToStructAny(p6, updateMap1) // just for compile
|
||||||
|
fmt.Println(ch6, err)
|
||||||
|
fmt.Printf("%+v\n", p6)
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
type Person struct {
|
||||||
|
Name string `json:"name"`
|
||||||
|
Age int `json:"age"`
|
||||||
|
Active bool `json:"active"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type Person2 struct {
|
||||||
|
Name string `json:"name"`
|
||||||
|
Age int `json:"age"`
|
||||||
|
Active bool `json:"active"`
|
||||||
|
}
|
||||||
@@ -0,0 +1,184 @@
|
|||||||
|
package structx
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"reflect"
|
||||||
|
"strconv"
|
||||||
|
|
||||||
|
"code.yun.ink/pkg/convx"
|
||||||
|
)
|
||||||
|
|
||||||
|
// converterFunc 类型转换函数
|
||||||
|
type converterFunc func(reflect.Value, string) (interface{}, error)
|
||||||
|
|
||||||
|
var (
|
||||||
|
typeConverters = map[reflect.Kind]converterFunc{
|
||||||
|
reflect.Bool: convertBool,
|
||||||
|
reflect.Int: convertInt[int],
|
||||||
|
reflect.Int8: convertInt[int8],
|
||||||
|
reflect.Int16: convertInt[int16],
|
||||||
|
reflect.Int32: convertInt[int32],
|
||||||
|
reflect.Int64: convertInt[int64],
|
||||||
|
reflect.Uint: convertUint[uint],
|
||||||
|
reflect.Uint8: convertUint[uint8],
|
||||||
|
reflect.Uint16: convertUint[uint16],
|
||||||
|
reflect.Uint32: convertUint[uint32],
|
||||||
|
reflect.Uint64: convertUint[uint64],
|
||||||
|
reflect.Float32: convertFloat[float32],
|
||||||
|
reflect.Float64: convertFloat[float64],
|
||||||
|
reflect.String: convertString,
|
||||||
|
reflect.Slice: convertSlice,
|
||||||
|
reflect.Array: convertArray,
|
||||||
|
reflect.Map: convertMap,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
// 转换为字符串
|
||||||
|
func convertToString(item interface{}) string {
|
||||||
|
if str, ok := item.(string); ok {
|
||||||
|
return str
|
||||||
|
}
|
||||||
|
return fmt.Sprintf("%v", item)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 转换为数字(float64)
|
||||||
|
func convertToFloat64(item interface{}) (float64, error) {
|
||||||
|
switch v := item.(type) {
|
||||||
|
case float64:
|
||||||
|
return v, nil
|
||||||
|
case int, int32, int64:
|
||||||
|
return float64(reflect.ValueOf(v).Int()), nil
|
||||||
|
case uint, uint32, uint64:
|
||||||
|
return float64(reflect.ValueOf(v).Uint()), nil
|
||||||
|
case float32:
|
||||||
|
return float64(v), nil
|
||||||
|
default:
|
||||||
|
return 0, fmt.Errorf("无法转换为数字")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 转换为类型别名
|
||||||
|
func convertToTypeAlias(aliasType reflect.Type, value interface{}) (interface{}, error) {
|
||||||
|
if aliasType.Kind() == reflect.Ptr {
|
||||||
|
elemType := aliasType.Elem()
|
||||||
|
newValue := reflect.New(elemType)
|
||||||
|
elemValue := newValue.Elem()
|
||||||
|
|
||||||
|
converted, err := convertValueToType(value, elemType)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
elemValue.Set(reflect.ValueOf(converted))
|
||||||
|
return newValue.Interface(), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return convertValueToType(value, aliasType)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 转换值为目标类型
|
||||||
|
func convertValueToType(value interface{}, targetType reflect.Type) (interface{}, error) {
|
||||||
|
valueType := reflect.TypeOf(value)
|
||||||
|
if valueType.AssignableTo(targetType) {
|
||||||
|
return value, nil
|
||||||
|
}
|
||||||
|
if valueType.ConvertibleTo(targetType) {
|
||||||
|
return reflect.ValueOf(value).Convert(targetType).Interface(), nil
|
||||||
|
}
|
||||||
|
return nil, fmt.Errorf("无法转换类型")
|
||||||
|
}
|
||||||
|
|
||||||
|
// 类型转换函数
|
||||||
|
func convertBool(field reflect.Value, value string) (interface{}, error) {
|
||||||
|
return convx.ToBool(value)
|
||||||
|
}
|
||||||
|
|
||||||
|
func convertInt[T int | int8 | int16 | int32 | int64](field reflect.Value, value string) (interface{}, error) {
|
||||||
|
bits := field.Type().Bits()
|
||||||
|
intVal, err := strconv.ParseInt(value, 10, bits)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return T(intVal), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func convertUint[T uint | uint8 | uint16 | uint32 | uint64](field reflect.Value, value string) (interface{}, error) {
|
||||||
|
bits := field.Type().Bits()
|
||||||
|
uintVal, err := strconv.ParseUint(value, 10, bits)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return T(uintVal), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func convertFloat[T float32 | float64](field reflect.Value, value string) (interface{}, error) {
|
||||||
|
bits := field.Type().Bits()
|
||||||
|
floatVal, err := strconv.ParseFloat(value, bits)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return T(floatVal), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func convertString(field reflect.Value, value string) (interface{}, error) {
|
||||||
|
return value, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func convertSlice(field reflect.Value, value string) (interface{}, error) {
|
||||||
|
var result []interface{}
|
||||||
|
if err := json.Unmarshal([]byte(value), &result); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return result, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func convertArray(field reflect.Value, value string) (interface{}, error) {
|
||||||
|
var result []interface{}
|
||||||
|
if err := json.Unmarshal([]byte(value), &result); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return result, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func convertMap(field reflect.Value, value string) (interface{}, error) {
|
||||||
|
return nil, fmt.Errorf("map转换未实现")
|
||||||
|
}
|
||||||
|
|
||||||
|
func getBaseTypeFromAlias(aliasType reflect.Type) reflect.Type {
|
||||||
|
if aliasType.Kind() == reflect.Ptr {
|
||||||
|
aliasType = aliasType.Elem()
|
||||||
|
}
|
||||||
|
|
||||||
|
switch aliasType.Kind() {
|
||||||
|
case reflect.String:
|
||||||
|
return reflect.TypeOf("")
|
||||||
|
case reflect.Int:
|
||||||
|
return reflect.TypeOf(int(0))
|
||||||
|
case reflect.Int8:
|
||||||
|
return reflect.TypeOf(int8(0))
|
||||||
|
case reflect.Int16:
|
||||||
|
return reflect.TypeOf(int16(0))
|
||||||
|
case reflect.Int32:
|
||||||
|
return reflect.TypeOf(int32(0))
|
||||||
|
case reflect.Int64:
|
||||||
|
return reflect.TypeOf(int64(0))
|
||||||
|
case reflect.Uint:
|
||||||
|
return reflect.TypeOf(uint(0))
|
||||||
|
case reflect.Uint8:
|
||||||
|
return reflect.TypeOf(uint8(0))
|
||||||
|
case reflect.Uint16:
|
||||||
|
return reflect.TypeOf(uint16(0))
|
||||||
|
case reflect.Uint32:
|
||||||
|
return reflect.TypeOf(uint32(0))
|
||||||
|
case reflect.Uint64:
|
||||||
|
return reflect.TypeOf(uint64(0))
|
||||||
|
case reflect.Float32:
|
||||||
|
return reflect.TypeOf(float32(0))
|
||||||
|
case reflect.Float64:
|
||||||
|
return reflect.TypeOf(float64(0))
|
||||||
|
case reflect.Bool:
|
||||||
|
return reflect.TypeOf(false)
|
||||||
|
default:
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,50 @@
|
|||||||
|
package structx
|
||||||
|
|
||||||
|
import (
|
||||||
|
"reflect"
|
||||||
|
)
|
||||||
|
|
||||||
|
// ChangeInfo 变更信息
|
||||||
|
type ChangeInfo struct {
|
||||||
|
Old string `json:"old"`
|
||||||
|
New string `json:"new"`
|
||||||
|
Val any `json:"val"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// FieldInfo 字段信息
|
||||||
|
type FieldInfo struct {
|
||||||
|
Index []int // 字段索引路径
|
||||||
|
Name string // 字段名
|
||||||
|
IsPtr bool // 是否为指针
|
||||||
|
FieldType reflect.Type // 字段类型
|
||||||
|
IsSlice bool // 是否为切片
|
||||||
|
IsArray bool // 是否为数组
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
basicStructTypes = map[string]bool{
|
||||||
|
"time.Time": true,
|
||||||
|
"github.com/shopspring/decimal.Decimal": true,
|
||||||
|
"sql.NullString": true,
|
||||||
|
"sql.NullInt64": true,
|
||||||
|
"sql.NullBool": true,
|
||||||
|
"sql.NullFloat64": true,
|
||||||
|
}
|
||||||
|
|
||||||
|
basicKinds = map[reflect.Kind]bool{
|
||||||
|
reflect.String: true,
|
||||||
|
reflect.Int: true,
|
||||||
|
reflect.Int8: true,
|
||||||
|
reflect.Int16: true,
|
||||||
|
reflect.Int32: true,
|
||||||
|
reflect.Int64: true,
|
||||||
|
reflect.Uint: true,
|
||||||
|
reflect.Uint8: true,
|
||||||
|
reflect.Uint16: true,
|
||||||
|
reflect.Uint32: true,
|
||||||
|
reflect.Uint64: true,
|
||||||
|
reflect.Float32: true,
|
||||||
|
reflect.Float64: true,
|
||||||
|
reflect.Bool: true,
|
||||||
|
}
|
||||||
|
)
|
||||||
@@ -0,0 +1,88 @@
|
|||||||
|
package structx
|
||||||
|
|
||||||
|
import (
|
||||||
|
"reflect"
|
||||||
|
"sync"
|
||||||
|
)
|
||||||
|
|
||||||
|
// FieldMapper 字段映射器接口
|
||||||
|
type FieldMapper interface {
|
||||||
|
GetFieldMap(t reflect.Type) map[string]FieldInfo
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
// 全局缓存 避免重复计算 TODO: 考虑使用LRU缓存
|
||||||
|
typeInfoCache = make(map[reflect.Type]map[string]FieldInfo)
|
||||||
|
cacheMutex = &sync.RWMutex{}
|
||||||
|
)
|
||||||
|
|
||||||
|
// defaultFieldMapper 默认字段映射器
|
||||||
|
type defaultFieldMapper struct{}
|
||||||
|
|
||||||
|
func (dm *defaultFieldMapper) GetFieldMap(t reflect.Type) map[string]FieldInfo {
|
||||||
|
|
||||||
|
cacheMutex.RLock()
|
||||||
|
if cached, exists := typeInfoCache[t]; exists {
|
||||||
|
cacheMutex.RUnlock()
|
||||||
|
return cached
|
||||||
|
}
|
||||||
|
cacheMutex.RUnlock()
|
||||||
|
|
||||||
|
fieldMap := make(map[string]FieldInfo)
|
||||||
|
dm.buildFieldMapRecursive(t, []int{}, fieldMap, "")
|
||||||
|
|
||||||
|
cacheMutex.Lock()
|
||||||
|
typeInfoCache[t] = fieldMap
|
||||||
|
cacheMutex.Unlock()
|
||||||
|
|
||||||
|
return fieldMap
|
||||||
|
}
|
||||||
|
|
||||||
|
// 递归构建字段映射表
|
||||||
|
func (dm *defaultFieldMapper) buildFieldMapRecursive(t reflect.Type, index []int, fieldMap map[string]FieldInfo, prefix string) {
|
||||||
|
for i := 0; i < t.NumField(); i++ {
|
||||||
|
field := t.Field(i)
|
||||||
|
if !field.IsExported() {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
currentIndex := append(index, i)
|
||||||
|
jsonTag := getJSONTagName(field)
|
||||||
|
|
||||||
|
fullKey := jsonTag
|
||||||
|
if prefix != "" {
|
||||||
|
fullKey = prefix + "." + jsonTag
|
||||||
|
}
|
||||||
|
|
||||||
|
fieldType := field.Type
|
||||||
|
isPtr := fieldType.Kind() == reflect.Ptr
|
||||||
|
actualType := fieldType
|
||||||
|
if isPtr {
|
||||||
|
// 解引用
|
||||||
|
actualType = fieldType.Elem()
|
||||||
|
}
|
||||||
|
|
||||||
|
if actualType.Kind() == reflect.Slice || actualType.Kind() == reflect.Array {
|
||||||
|
fieldMap[fullKey] = FieldInfo{
|
||||||
|
Index: currentIndex,
|
||||||
|
Name: field.Name,
|
||||||
|
IsPtr: isPtr,
|
||||||
|
FieldType: fieldType,
|
||||||
|
IsSlice: actualType.Kind() == reflect.Slice,
|
||||||
|
IsArray: actualType.Kind() == reflect.Array,
|
||||||
|
}
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if actualType.Kind() == reflect.Struct && !isBasicStructType(actualType) {
|
||||||
|
dm.buildFieldMapRecursive(actualType, currentIndex, fieldMap, fullKey)
|
||||||
|
}
|
||||||
|
|
||||||
|
fieldMap[fullKey] = FieldInfo{
|
||||||
|
Index: currentIndex,
|
||||||
|
Name: field.Name,
|
||||||
|
IsPtr: isPtr,
|
||||||
|
FieldType: fieldType,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,5 +1,11 @@
|
|||||||
module code.yun.ink/pkg/structx
|
module code.yun.ink/pkg/structx
|
||||||
|
|
||||||
go 1.20
|
go 1.21.0
|
||||||
|
|
||||||
require code.yun.ink/pkg/convx v1.0.1
|
toolchain go1.22.4
|
||||||
|
|
||||||
|
require (
|
||||||
|
code.yun.ink/pkg/convx v1.0.3
|
||||||
|
github.com/shopspring/decimal v1.4.0
|
||||||
|
github.com/spf13/cast v1.10.0
|
||||||
|
)
|
||||||
|
|||||||
@@ -1,2 +1,16 @@
|
|||||||
code.yun.ink/pkg/convx v1.0.1 h1:T6yIVqyC1nQc9CO0C7iHn5rs7GxbaulmrJtxwg95q9Y=
|
code.yun.ink/pkg/convx v1.0.3 h1:pH8dUOgsoaBYVQ3+4C2+uVua561nDxq6/GpaQ9wnCew=
|
||||||
code.yun.ink/pkg/convx v1.0.1/go.mod h1:6xqmUend1kwarRvJ0TQlfzzS4QCWdRrXQiUY/ggzYqo=
|
code.yun.ink/pkg/convx v1.0.3/go.mod h1:6xqmUend1kwarRvJ0TQlfzzS4QCWdRrXQiUY/ggzYqo=
|
||||||
|
github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8=
|
||||||
|
github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0=
|
||||||
|
github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=
|
||||||
|
github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
||||||
|
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
|
||||||
|
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
|
||||||
|
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
||||||
|
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
|
||||||
|
github.com/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8=
|
||||||
|
github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs=
|
||||||
|
github.com/shopspring/decimal v1.4.0 h1:bxl37RwXBklmTi0C79JfXCEBD1cqqHt0bbgBAGFp81k=
|
||||||
|
github.com/shopspring/decimal v1.4.0/go.mod h1:gawqmDU56v4yIKSwfBSFip1HdCCXN8/+DMd9qYNcwME=
|
||||||
|
github.com/spf13/cast v1.10.0 h1:h2x0u2shc1QuLHfxi+cTJvs30+ZAHOGRic8uyGTDWxY=
|
||||||
|
github.com/spf13/cast v1.10.0/go.mod h1:jNfB8QC9IA6ZuY2ZjDp0KtFO2LZZlg4S/7bzP6qqeHo=
|
||||||
|
|||||||
+19
@@ -0,0 +1,19 @@
|
|||||||
|
package structx
|
||||||
|
|
||||||
|
type Options struct {
|
||||||
|
// map允许传入struct不存在的字段
|
||||||
|
allowUnknownFields bool
|
||||||
|
}
|
||||||
|
|
||||||
|
type Option func(*Options)
|
||||||
|
|
||||||
|
func defaultOptions() *Options {
|
||||||
|
return &Options{}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 允许传入struct不存在的字段
|
||||||
|
func AllowUnknownFields() Option {
|
||||||
|
return func(o *Options) {
|
||||||
|
o.allowUnknownFields = true
|
||||||
|
}
|
||||||
|
}
|
||||||
+234
-135
@@ -1,158 +1,257 @@
|
|||||||
package structx
|
package structx
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"reflect"
|
"reflect"
|
||||||
"strconv"
|
|
||||||
"strings"
|
"github.com/spf13/cast"
|
||||||
"code.yun.ink/pkg/convx"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type ChangeInfo struct {
|
// 全局函数(保持向后兼容)
|
||||||
Old string `json:"old"`
|
func AttactToStructAny(structxx any, updateMap map[string]any) (map[string]ChangeInfo, error) {
|
||||||
New string `json:"new"`
|
return NewStructProcessor().AttactToStructAny(structxx, updateMap)
|
||||||
Val interface{} `json:"val"`
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 这个方法将map的数据赋值到结构体中
|
func AttactToStruct(structxx any, updateMap map[string]string) (map[string]ChangeInfo, error) {
|
||||||
func AttactToStruct(structxx interface{}, updateMap map[string]string) (map[string]ChangeInfo, error) {
|
return NewStructProcessor().AttactToStruct(structxx, updateMap)
|
||||||
// 将结构体指针转换为reflect.Value类型
|
}
|
||||||
|
|
||||||
|
// StructProcessor 结构体处理器
|
||||||
|
type StructProcessor struct {
|
||||||
|
options *Options
|
||||||
|
fieldMapper FieldMapper
|
||||||
|
valueSetter ValueSetter
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewStructProcessor 创建新的结构体处理器
|
||||||
|
func NewStructProcessor(ops ...Option) *StructProcessor {
|
||||||
|
|
||||||
|
options := defaultOptions()
|
||||||
|
for _, op := range ops {
|
||||||
|
op(options)
|
||||||
|
}
|
||||||
|
|
||||||
|
return &StructProcessor{
|
||||||
|
options: options,
|
||||||
|
fieldMapper: &defaultFieldMapper{},
|
||||||
|
valueSetter: &defaultValueSetter{},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// AttactToStructAny 将 map[string]interface{} 转换为字符串映射并调用 AttactToStruct
|
||||||
|
func (sp *StructProcessor) AttactToStructAny(structxx any, updateMap map[string]any) (map[string]ChangeInfo, error) {
|
||||||
|
stringMap := make(map[string]string, len(updateMap))
|
||||||
|
for k, v := range updateMap {
|
||||||
|
str, err := cast.ToStringE(v)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("转换键 %s 的值失败: %w", k, err)
|
||||||
|
}
|
||||||
|
stringMap[k] = str
|
||||||
|
}
|
||||||
|
return sp.AttactToStruct(structxx, stringMap)
|
||||||
|
}
|
||||||
|
|
||||||
|
// AttactToStruct 将映射数据赋值到结构体中
|
||||||
|
func (sp *StructProcessor) AttactToStruct(structxx any, updateMap map[string]string) (map[string]ChangeInfo, error) {
|
||||||
v := reflect.ValueOf(structxx)
|
v := reflect.ValueOf(structxx)
|
||||||
// 如果v不是指针或者v是零值,直接返回
|
|
||||||
if v.Kind() != reflect.Ptr || v.IsNil() {
|
if v.Kind() != reflect.Ptr || v.IsNil() {
|
||||||
return nil, fmt.Errorf("structxx 需要是指针")
|
return nil, fmt.Errorf("structxx 需要是非空指针")
|
||||||
}
|
}
|
||||||
|
|
||||||
changeMap := make(map[string]ChangeInfo)
|
changeMap := make(map[string]ChangeInfo)
|
||||||
|
|
||||||
// 获取v指向的元素,也就是结构体本身
|
|
||||||
v = v.Elem()
|
v = v.Elem()
|
||||||
// 遍历map的键和值
|
|
||||||
for k, val := range updateMap {
|
|
||||||
// 根据json标签获取结构体中对应的字段
|
|
||||||
field := v.FieldByNameFunc(func(name string) bool {
|
|
||||||
f, _ := v.Type().FieldByName(name)
|
|
||||||
// fmt.Printf("FieldByNameFunc:%+v\n", f)
|
|
||||||
js := f.Tag.Get("json")
|
|
||||||
s := strings.Split(js, ",")
|
|
||||||
if len(s) > 1 {
|
|
||||||
return s[0] == k
|
|
||||||
}
|
|
||||||
return js == k
|
|
||||||
})
|
|
||||||
// 如果字段存在且可写
|
|
||||||
if field.IsValid() && field.CanSet() {
|
|
||||||
// 根据字段的类型进行类型转换和赋值
|
|
||||||
switch field.Kind() {
|
|
||||||
// convx.
|
|
||||||
case reflect.Int64:
|
|
||||||
// 将字符串转换为int64
|
|
||||||
oldi := field.Int()
|
|
||||||
olds, _ := convx.ToString(oldi)
|
|
||||||
|
|
||||||
i, _ := strconv.ParseInt(val, 10, 64)
|
t := v.Type()
|
||||||
field.SetInt(i)
|
|
||||||
|
|
||||||
changeMap[k] = ChangeInfo{Old: olds, New: val, Val: i}
|
fieldMap := sp.fieldMapper.GetFieldMap(t)
|
||||||
case reflect.String:
|
|
||||||
olds := field.String()
|
|
||||||
|
|
||||||
field.SetString(val)
|
for mapKey, mapValue := range updateMap {
|
||||||
|
fieldInfo, exists := fieldMap[mapKey]
|
||||||
changeMap[k] = ChangeInfo{Old: olds, New: val, Val: val}
|
if !exists {
|
||||||
case reflect.Int:
|
if sp.options.allowUnknownFields {
|
||||||
oldi := field.Int()
|
continue
|
||||||
olds, _ := convx.ToString(oldi)
|
} else {
|
||||||
|
return nil, fmt.Errorf("字段 %s 不存在", mapKey)
|
||||||
i, _ := convx.ToInt64(val)
|
|
||||||
field.SetInt(i)
|
|
||||||
|
|
||||||
changeMap[k] = ChangeInfo{Old: olds, New: val, Val: i}
|
|
||||||
case reflect.Int32:
|
|
||||||
oldi := field.Int()
|
|
||||||
olds, _ := convx.ToString(oldi)
|
|
||||||
|
|
||||||
i, _ := convx.ToInt64(val)
|
|
||||||
field.SetInt(i)
|
|
||||||
|
|
||||||
changeMap[k] = ChangeInfo{Old: olds, New: val, Val: i}
|
|
||||||
case reflect.Int16:
|
|
||||||
oldi := field.Int()
|
|
||||||
olds, _ := convx.ToString(oldi)
|
|
||||||
|
|
||||||
i, _ := convx.ToInt64(val)
|
|
||||||
field.SetInt(i)
|
|
||||||
|
|
||||||
changeMap[k] = ChangeInfo{Old: olds, New: val, Val: i}
|
|
||||||
case reflect.Int8:
|
|
||||||
oldi := field.Int()
|
|
||||||
olds, _ := convx.ToString(oldi)
|
|
||||||
|
|
||||||
i, _ := convx.ToInt64(val)
|
|
||||||
field.SetInt(i)
|
|
||||||
|
|
||||||
changeMap[k] = ChangeInfo{Old: olds, New: val, Val: i}
|
|
||||||
case reflect.Uint:
|
|
||||||
oldi := field.Uint()
|
|
||||||
olds, _ := convx.ToString(oldi)
|
|
||||||
|
|
||||||
i, _ := convx.ToInt64(val)
|
|
||||||
field.SetInt(i)
|
|
||||||
|
|
||||||
changeMap[k] = ChangeInfo{Old: olds, New: val, Val: i}
|
|
||||||
case reflect.Uint32:
|
|
||||||
oldi := field.Uint()
|
|
||||||
olds, _ := convx.ToString(oldi)
|
|
||||||
|
|
||||||
i, _ := convx.ToInt64(val)
|
|
||||||
field.SetInt(i)
|
|
||||||
|
|
||||||
changeMap[k] = ChangeInfo{Old: olds, New: val, Val: i}
|
|
||||||
case reflect.Uint16:
|
|
||||||
oldi := field.Uint()
|
|
||||||
olds, _ := convx.ToString(oldi)
|
|
||||||
|
|
||||||
i, _ := convx.ToInt64(val)
|
|
||||||
field.SetInt(i)
|
|
||||||
|
|
||||||
changeMap[k] = ChangeInfo{Old: olds, New: val, Val: i}
|
|
||||||
case reflect.Uint8:
|
|
||||||
oldi := field.Uint()
|
|
||||||
olds, _ := convx.ToString(oldi)
|
|
||||||
|
|
||||||
i, _ := convx.ToInt64(val)
|
|
||||||
field.SetInt(i)
|
|
||||||
|
|
||||||
changeMap[k] = ChangeInfo{Old: olds, New: val, Val: i}
|
|
||||||
case reflect.Uint64:
|
|
||||||
oldi := field.Uint()
|
|
||||||
olds, _ := convx.ToString(oldi)
|
|
||||||
|
|
||||||
i, _ := convx.ToInt64(val)
|
|
||||||
field.SetInt(i)
|
|
||||||
|
|
||||||
changeMap[k] = ChangeInfo{Old: olds, New: val, Val: i}
|
|
||||||
case reflect.Float32:
|
|
||||||
oldi := field.Float()
|
|
||||||
olds, _ := convx.ToString(oldi)
|
|
||||||
|
|
||||||
i, _ := convx.ToFloat64(val)
|
|
||||||
field.SetFloat(i)
|
|
||||||
|
|
||||||
changeMap[k] = ChangeInfo{Old: olds, New: val, Val: i}
|
|
||||||
case reflect.Float64:
|
|
||||||
oldi := field.Float()
|
|
||||||
olds, _ := convx.ToString(oldi)
|
|
||||||
|
|
||||||
i, _ := convx.ToFloat64(val)
|
|
||||||
field.SetFloat(i)
|
|
||||||
|
|
||||||
changeMap[k] = ChangeInfo{Old: olds, New: val, Val: i}
|
|
||||||
default:
|
|
||||||
fmt.Println("未知类型")
|
|
||||||
return nil, fmt.Errorf("未知类型:" + k)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
field, err := sp.getFieldByPath(v, fieldInfo.Index)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("获取字段 %s 失败: %w", mapKey, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if !field.IsValid() {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// 处理指针字段
|
||||||
|
if fieldInfo.IsPtr {
|
||||||
|
if field.Kind() != reflect.Ptr {
|
||||||
|
return nil, fmt.Errorf("字段 %s 应该是指针类型", mapKey)
|
||||||
|
}
|
||||||
|
if field.IsNil() {
|
||||||
|
field.Set(reflect.New(fieldInfo.FieldType.Elem()))
|
||||||
|
}
|
||||||
|
field = field.Elem()
|
||||||
|
}
|
||||||
|
|
||||||
|
// 处理切片和数组
|
||||||
|
if fieldInfo.IsSlice || fieldInfo.IsArray {
|
||||||
|
if err := sp.processSliceOrArrayField(field, mapValue, mapKey); err != nil {
|
||||||
|
return nil, fmt.Errorf("处理切片/数组字段 %s 失败: %w", mapKey, err)
|
||||||
|
}
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// 处理嵌套结构体
|
||||||
|
if field.Kind() == reflect.Struct && !isBasicStructType(field.Type()) {
|
||||||
|
nestedChanges, err := sp.processNestedStruct(field, mapValue, mapKey)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("处理嵌套结构体字段 %s 失败: %w", mapKey, err)
|
||||||
|
}
|
||||||
|
for nestedKey, change := range nestedChanges {
|
||||||
|
changeMap[nestedKey] = change
|
||||||
|
}
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if !field.CanSet() {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
oldValueStr, _ := cast.ToStringE(field.Interface())
|
||||||
|
newValue, err := sp.valueSetter.SetFieldValue(field, mapValue)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("设置字段 %s 的值失败: %w", mapKey, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
newValueStr, _ := cast.ToStringE(newValue)
|
||||||
|
changeMap[mapKey] = ChangeInfo{
|
||||||
|
Old: oldValueStr,
|
||||||
|
New: newValueStr,
|
||||||
|
Val: newValue,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return changeMap, nil
|
return changeMap, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// getFieldByPath 通过路径获取字段
|
||||||
|
func (sp *StructProcessor) getFieldByPath(v reflect.Value, index []int) (reflect.Value, error) {
|
||||||
|
if len(index) == 0 {
|
||||||
|
return v, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
field := v.Field(index[0])
|
||||||
|
if !field.IsValid() {
|
||||||
|
return reflect.Value{}, fmt.Errorf("字段索引 %d 无效", index[0])
|
||||||
|
}
|
||||||
|
|
||||||
|
// 处理指针
|
||||||
|
for field.Kind() == reflect.Ptr {
|
||||||
|
if field.IsNil() {
|
||||||
|
field.Set(reflect.New(field.Type().Elem()))
|
||||||
|
}
|
||||||
|
field = field.Elem()
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(index) > 1 {
|
||||||
|
return sp.getFieldByPath(field, index[1:])
|
||||||
|
}
|
||||||
|
|
||||||
|
return field, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// processSliceOrArrayField 处理切片和数组字段
|
||||||
|
func (sp *StructProcessor) processSliceOrArrayField(field reflect.Value, value string, fieldKey string) error {
|
||||||
|
var jsonData []interface{}
|
||||||
|
if err := json.Unmarshal([]byte(value), &jsonData); err != nil {
|
||||||
|
return fmt.Errorf("字段 %s 的值必须是JSON数组格式: %w", fieldKey, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
fieldType := field.Type()
|
||||||
|
elemType := fieldType.Elem()
|
||||||
|
|
||||||
|
var newContainer reflect.Value
|
||||||
|
if fieldType.Kind() == reflect.Slice {
|
||||||
|
newContainer = reflect.MakeSlice(fieldType, len(jsonData), len(jsonData))
|
||||||
|
} else if fieldType.Kind() == reflect.Array {
|
||||||
|
if len(jsonData) != fieldType.Len() {
|
||||||
|
return fmt.Errorf("字段 %s 的数组长度不匹配", fieldKey)
|
||||||
|
}
|
||||||
|
newContainer = reflect.New(fieldType).Elem()
|
||||||
|
} else {
|
||||||
|
return fmt.Errorf("字段 %s 不是切片或数组类型", fieldKey)
|
||||||
|
}
|
||||||
|
|
||||||
|
for i, item := range jsonData {
|
||||||
|
if err := sp.valueSetter.SetSliceElementValue(newContainer.Index(i), item, elemType); err != nil {
|
||||||
|
return fmt.Errorf("设置切片元素失败: %w", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
field.Set(newContainer)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// processNestedStruct 处理嵌套结构体
|
||||||
|
func (sp *StructProcessor) processNestedStruct(field reflect.Value, value string, parentKey string) (map[string]ChangeInfo, error) {
|
||||||
|
changeMap := make(map[string]ChangeInfo)
|
||||||
|
|
||||||
|
var structValue reflect.Value
|
||||||
|
if field.Kind() == reflect.Ptr {
|
||||||
|
if field.IsNil() {
|
||||||
|
field.Set(reflect.New(field.Type().Elem()))
|
||||||
|
}
|
||||||
|
structValue = field.Elem()
|
||||||
|
} else {
|
||||||
|
structValue = field
|
||||||
|
}
|
||||||
|
|
||||||
|
if !structValue.IsValid() || structValue.Kind() != reflect.Struct {
|
||||||
|
return nil, fmt.Errorf("无效的结构体字段")
|
||||||
|
}
|
||||||
|
|
||||||
|
// 尝试解析为JSON对象
|
||||||
|
var nestedMap map[string]interface{}
|
||||||
|
if err := json.Unmarshal([]byte(value), &nestedMap); err == nil {
|
||||||
|
stringMap := make(map[string]string, len(nestedMap))
|
||||||
|
for k, v := range nestedMap {
|
||||||
|
str, err := cast.ToStringE(v)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("转换嵌套字段 %s 的值失败: %w", k, err)
|
||||||
|
}
|
||||||
|
stringMap[k] = str
|
||||||
|
}
|
||||||
|
|
||||||
|
nestedChanges, err := sp.AttactToStruct(structValue.Addr().Interface(), stringMap)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
for key, change := range nestedChanges {
|
||||||
|
changeMap[parentKey+"."+key] = change
|
||||||
|
}
|
||||||
|
return changeMap, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// 尝试直接设置值
|
||||||
|
if hasUnmarshalJSON(structValue.Type()) || isBasicStructType(structValue.Type()) {
|
||||||
|
oldValueStr, _ := cast.ToStringE(field.Interface())
|
||||||
|
|
||||||
|
if err := setBasicStructValue(structValue, value); err != nil {
|
||||||
|
return nil, fmt.Errorf("设置基本结构体值失败: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
newValueStr, _ := cast.ToStringE(field.Interface())
|
||||||
|
changeMap[parentKey] = ChangeInfo{
|
||||||
|
Old: oldValueStr,
|
||||||
|
New: newValueStr,
|
||||||
|
Val: structValue.Interface(),
|
||||||
|
}
|
||||||
|
return changeMap, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil, fmt.Errorf("嵌套结构体值必须是有效的JSON格式")
|
||||||
|
}
|
||||||
|
|||||||
+184
@@ -0,0 +1,184 @@
|
|||||||
|
package structx
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"reflect"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"code.yun.ink/pkg/convx"
|
||||||
|
)
|
||||||
|
|
||||||
|
type ChangeInfo struct {
|
||||||
|
Old string `json:"old"`
|
||||||
|
New string `json:"new"`
|
||||||
|
Val interface{} `json:"val"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// map[string]interface类型的值附加到结构体中
|
||||||
|
func AttactToStructAny(structxx interface{}, updateMap map[string]interface{}) (map[string]ChangeInfo, error) {
|
||||||
|
m := map[string]string{}
|
||||||
|
for k, v := range updateMap {
|
||||||
|
str, err := convx.ToString(v)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
m[k] = str
|
||||||
|
}
|
||||||
|
|
||||||
|
// fmt.Println("bef", m, structxx)
|
||||||
|
|
||||||
|
r, err := AttactToStruct(structxx, m)
|
||||||
|
// fmt.Println("resp:", r, m, structxx)
|
||||||
|
return r, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// 这个方法将map的数据赋值到结构体中
|
||||||
|
func AttactToStruct(structxx interface{}, updateMap map[string]string) (map[string]ChangeInfo, error) {
|
||||||
|
// 将结构体指针转换为reflect.Value类型
|
||||||
|
v := reflect.ValueOf(structxx)
|
||||||
|
// 如果v不是指针或者v是零值,直接返回
|
||||||
|
if v.Kind() != reflect.Ptr || v.IsNil() {
|
||||||
|
return nil, fmt.Errorf("structxx 需要是指针")
|
||||||
|
}
|
||||||
|
|
||||||
|
changeMap := make(map[string]ChangeInfo)
|
||||||
|
|
||||||
|
// 获取v指向的元素,也就是结构体本身
|
||||||
|
v = v.Elem()
|
||||||
|
// 遍历map的键和值
|
||||||
|
for k, val := range updateMap {
|
||||||
|
// 根据json标签获取结构体中对应的字段
|
||||||
|
field := v.FieldByNameFunc(func(name string) bool {
|
||||||
|
f, _ := v.Type().FieldByName(name)
|
||||||
|
// fmt.Printf("FieldByNameFunc:%+v\n", f)
|
||||||
|
js := f.Tag.Get("json")
|
||||||
|
s := strings.Split(js, ",")
|
||||||
|
if len(s) > 1 {
|
||||||
|
return s[0] == k
|
||||||
|
}
|
||||||
|
return js == k
|
||||||
|
})
|
||||||
|
// 如果字段存在且可写
|
||||||
|
if field.IsValid() && field.CanSet() {
|
||||||
|
// 根据字段的类型进行类型转换和赋值
|
||||||
|
switch field.Kind() {
|
||||||
|
case reflect.Bool:
|
||||||
|
oldb := field.Bool()
|
||||||
|
olds, _ := convx.ToString(oldb)
|
||||||
|
|
||||||
|
b, _ := convx.ToBool(val)
|
||||||
|
field.SetBool(b)
|
||||||
|
|
||||||
|
changeMap[k] = ChangeInfo{Old: olds, New: val, Val: b}
|
||||||
|
case reflect.Int64:
|
||||||
|
// 将字符串转换为int64
|
||||||
|
oldi := field.Int()
|
||||||
|
olds, _ := convx.ToString(oldi)
|
||||||
|
|
||||||
|
i, _ := strconv.ParseInt(val, 10, 64)
|
||||||
|
field.SetInt(i)
|
||||||
|
|
||||||
|
changeMap[k] = ChangeInfo{Old: olds, New: val, Val: i}
|
||||||
|
case reflect.String:
|
||||||
|
olds := field.String()
|
||||||
|
|
||||||
|
field.SetString(val)
|
||||||
|
|
||||||
|
changeMap[k] = ChangeInfo{Old: olds, New: val, Val: val}
|
||||||
|
case reflect.Int:
|
||||||
|
oldi := field.Int()
|
||||||
|
olds, _ := convx.ToString(oldi)
|
||||||
|
|
||||||
|
i, _ := convx.ToInt64(val)
|
||||||
|
field.SetInt(i)
|
||||||
|
|
||||||
|
changeMap[k] = ChangeInfo{Old: olds, New: val, Val: i}
|
||||||
|
case reflect.Int32:
|
||||||
|
oldi := field.Int()
|
||||||
|
olds, _ := convx.ToString(oldi)
|
||||||
|
|
||||||
|
i, _ := convx.ToInt64(val)
|
||||||
|
field.SetInt(i)
|
||||||
|
|
||||||
|
changeMap[k] = ChangeInfo{Old: olds, New: val, Val: i}
|
||||||
|
case reflect.Int16:
|
||||||
|
oldi := field.Int()
|
||||||
|
olds, _ := convx.ToString(oldi)
|
||||||
|
|
||||||
|
i, _ := convx.ToInt64(val)
|
||||||
|
field.SetInt(i)
|
||||||
|
|
||||||
|
changeMap[k] = ChangeInfo{Old: olds, New: val, Val: i}
|
||||||
|
case reflect.Int8:
|
||||||
|
oldi := field.Int()
|
||||||
|
olds, _ := convx.ToString(oldi)
|
||||||
|
|
||||||
|
i, _ := convx.ToInt64(val)
|
||||||
|
field.SetInt(i)
|
||||||
|
|
||||||
|
changeMap[k] = ChangeInfo{Old: olds, New: val, Val: i}
|
||||||
|
case reflect.Uint:
|
||||||
|
oldi := field.Uint()
|
||||||
|
olds, _ := convx.ToString(oldi)
|
||||||
|
|
||||||
|
i, _ := convx.ToInt64(val)
|
||||||
|
field.SetInt(i)
|
||||||
|
|
||||||
|
changeMap[k] = ChangeInfo{Old: olds, New: val, Val: i}
|
||||||
|
case reflect.Uint32:
|
||||||
|
oldi := field.Uint()
|
||||||
|
olds, _ := convx.ToString(oldi)
|
||||||
|
|
||||||
|
i, _ := convx.ToInt64(val)
|
||||||
|
field.SetInt(i)
|
||||||
|
|
||||||
|
changeMap[k] = ChangeInfo{Old: olds, New: val, Val: i}
|
||||||
|
case reflect.Uint16:
|
||||||
|
oldi := field.Uint()
|
||||||
|
olds, _ := convx.ToString(oldi)
|
||||||
|
|
||||||
|
i, _ := convx.ToInt64(val)
|
||||||
|
field.SetInt(i)
|
||||||
|
|
||||||
|
changeMap[k] = ChangeInfo{Old: olds, New: val, Val: i}
|
||||||
|
case reflect.Uint8:
|
||||||
|
oldi := field.Uint()
|
||||||
|
olds, _ := convx.ToString(oldi)
|
||||||
|
|
||||||
|
i, _ := convx.ToInt64(val)
|
||||||
|
field.SetInt(i)
|
||||||
|
|
||||||
|
changeMap[k] = ChangeInfo{Old: olds, New: val, Val: i}
|
||||||
|
case reflect.Uint64:
|
||||||
|
oldi := field.Uint()
|
||||||
|
olds, _ := convx.ToString(oldi)
|
||||||
|
|
||||||
|
i, _ := convx.ToInt64(val)
|
||||||
|
field.SetInt(i)
|
||||||
|
|
||||||
|
changeMap[k] = ChangeInfo{Old: olds, New: val, Val: i}
|
||||||
|
case reflect.Float32:
|
||||||
|
oldi := field.Float()
|
||||||
|
olds, _ := convx.ToString(oldi)
|
||||||
|
|
||||||
|
i, _ := convx.ToFloat64(val)
|
||||||
|
field.SetFloat(i)
|
||||||
|
|
||||||
|
changeMap[k] = ChangeInfo{Old: olds, New: val, Val: i}
|
||||||
|
case reflect.Float64:
|
||||||
|
oldi := field.Float()
|
||||||
|
olds, _ := convx.ToString(oldi)
|
||||||
|
|
||||||
|
i, _ := convx.ToFloat64(val)
|
||||||
|
field.SetFloat(i)
|
||||||
|
|
||||||
|
changeMap[k] = ChangeInfo{Old: olds, New: val, Val: i}
|
||||||
|
default:
|
||||||
|
fmt.Println("未知类型")
|
||||||
|
return nil, fmt.Errorf("未知类型:" + k)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return changeMap, nil
|
||||||
|
}
|
||||||
+873
@@ -0,0 +1,873 @@
|
|||||||
|
package structx_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"code.yun.ink/pkg/structx"
|
||||||
|
"github.com/shopspring/decimal"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestAttactToStructMap(t *testing.T) {
|
||||||
|
|
||||||
|
m := make(map[string]interface{})
|
||||||
|
m["name"] = "Tom"
|
||||||
|
m["age"] = 18
|
||||||
|
m["is_man"] = true
|
||||||
|
fmt.Println(m)
|
||||||
|
|
||||||
|
d := Data{}
|
||||||
|
|
||||||
|
r, err := structx.AttactToStructAny(&d, m)
|
||||||
|
|
||||||
|
fmt.Printf("%+v %+v %+v", d, r, err)
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
type Data struct {
|
||||||
|
Name string `json:"name"`
|
||||||
|
Age int `json:"age"`
|
||||||
|
IsMan bool `json:"is_man"`
|
||||||
|
Addr string `json:"addr"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// 基础测试结构体
|
||||||
|
type BasicStruct struct {
|
||||||
|
Name string `json:"name"`
|
||||||
|
Age int `json:"age"`
|
||||||
|
Salary float64 `json:"salary"`
|
||||||
|
IsActive bool `json:"is_active"`
|
||||||
|
Count uint `json:"count"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// 嵌套结构体
|
||||||
|
type NestedStruct struct {
|
||||||
|
Basic BasicStruct `json:"basic"`
|
||||||
|
Comment string `json:"comment"`
|
||||||
|
Amount decimal.Decimal `json:"amount"`
|
||||||
|
Amount2 *decimal.Decimal `json:"amount2"`
|
||||||
|
Timestamp time.Time `json:"timestamp"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// 指针嵌套结构体
|
||||||
|
type PointerStruct struct {
|
||||||
|
Basic *BasicStruct `json:"basic"`
|
||||||
|
Enabled bool `json:"enabled"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// 多层嵌套结构体
|
||||||
|
type MultiLevelStruct struct {
|
||||||
|
Nested NestedStruct `json:"nested"`
|
||||||
|
Pointer *PointerStruct `json:"pointer"`
|
||||||
|
Tags []string `json:"tags"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// 自定义类型
|
||||||
|
type CustomString string
|
||||||
|
type CustomInt int
|
||||||
|
|
||||||
|
type CustomTypeStruct struct {
|
||||||
|
ID CustomString `json:"id"`
|
||||||
|
Version CustomInt `json:"version"`
|
||||||
|
Email string `json:"email"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// 实现 TextUnmarshaler 接口的类型
|
||||||
|
type CustomUnmarshaler string
|
||||||
|
|
||||||
|
func (c *CustomUnmarshaler) UnmarshalText(text []byte) error {
|
||||||
|
*c = CustomUnmarshaler("custom_" + string(text))
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type UnmarshalerStruct struct {
|
||||||
|
Data CustomUnmarshaler `json:"data"`
|
||||||
|
Name string `json:"name"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// 复杂嵌套结构体
|
||||||
|
type ComplexStruct struct {
|
||||||
|
Basic BasicStruct `json:"basic"`
|
||||||
|
Nested *NestedStruct `json:"nested"`
|
||||||
|
Custom CustomTypeStruct `json:"custom"`
|
||||||
|
Timestamp time.Time `json:"timestamp"`
|
||||||
|
Metadata map[string]string `json:"metadata"`
|
||||||
|
Unmarshaler CustomUnmarshaler `json:"unmarshaler"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// 基础类型测试
|
||||||
|
func TestAttactToStruct_BasicTypes(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
input map[string]string
|
||||||
|
expected BasicStruct
|
||||||
|
wantErr bool
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "所有基础类型",
|
||||||
|
input: map[string]string{
|
||||||
|
"name": "John Doe",
|
||||||
|
"age": "30",
|
||||||
|
"salary": "50000.50",
|
||||||
|
"is_active": "true",
|
||||||
|
"count": "100",
|
||||||
|
},
|
||||||
|
expected: BasicStruct{
|
||||||
|
Name: "John Doe",
|
||||||
|
Age: 30,
|
||||||
|
Salary: 50000.50,
|
||||||
|
IsActive: true,
|
||||||
|
Count: 100,
|
||||||
|
},
|
||||||
|
wantErr: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "部分字段",
|
||||||
|
input: map[string]string{
|
||||||
|
"name": "Alice",
|
||||||
|
"age": "25",
|
||||||
|
},
|
||||||
|
expected: BasicStruct{
|
||||||
|
Name: "Alice",
|
||||||
|
Age: 25,
|
||||||
|
},
|
||||||
|
wantErr: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "无效布尔值",
|
||||||
|
input: map[string]string{
|
||||||
|
"is_active": "invalid",
|
||||||
|
},
|
||||||
|
wantErr: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "无效数字",
|
||||||
|
input: map[string]string{
|
||||||
|
"age": "not_a_number",
|
||||||
|
},
|
||||||
|
wantErr: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "空字符串处理",
|
||||||
|
input: map[string]string{
|
||||||
|
"name": "",
|
||||||
|
"age": "0",
|
||||||
|
},
|
||||||
|
expected: BasicStruct{
|
||||||
|
Name: "",
|
||||||
|
Age: 0,
|
||||||
|
},
|
||||||
|
wantErr: false,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
var s BasicStruct
|
||||||
|
changes, err := structx.AttactToStruct(&s, tt.input)
|
||||||
|
|
||||||
|
if tt.wantErr {
|
||||||
|
if err == nil {
|
||||||
|
t.Errorf("期望错误,但得到 nil")
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("意外的错误: %v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if s != tt.expected {
|
||||||
|
t.Errorf("期望 %+v, 得到 %+v", tt.expected, s)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 验证变更记录
|
||||||
|
if len(changes) != len(tt.input) {
|
||||||
|
t.Errorf("期望 %d 个变更记录, 得到 %d", len(tt.input), len(changes))
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 测试嵌套结构体
|
||||||
|
func TestAttactToStruct_NestedStruct(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
input map[string]string
|
||||||
|
expected NestedStruct
|
||||||
|
wantErr bool
|
||||||
|
}{
|
||||||
|
// {
|
||||||
|
// name: "嵌套结构体赋值",
|
||||||
|
// input: map[string]string{
|
||||||
|
// "basic.name": "John",
|
||||||
|
// "basic.age": "30",
|
||||||
|
// "basic.salary": "50000.0",
|
||||||
|
// "basic.is_active": "true",
|
||||||
|
// "comment": "test comment",
|
||||||
|
// },
|
||||||
|
// expected: NestedStruct{
|
||||||
|
// Basic: BasicStruct{
|
||||||
|
// Name: "John",
|
||||||
|
// Age: 30,
|
||||||
|
// Salary: 50000.0,
|
||||||
|
// IsActive: true,
|
||||||
|
// },
|
||||||
|
// Comment: "test comment",
|
||||||
|
// },
|
||||||
|
// wantErr: false,
|
||||||
|
// },
|
||||||
|
{
|
||||||
|
name: "部分嵌套字段",
|
||||||
|
input: map[string]string{
|
||||||
|
"basic.name": "Alice",
|
||||||
|
"comment": "partial",
|
||||||
|
"amount": "123.45",
|
||||||
|
"amount2": "123.45", // 测试指针字段
|
||||||
|
"timestamp": "2024-01-01T15:04:05Z",
|
||||||
|
},
|
||||||
|
expected: NestedStruct{
|
||||||
|
Basic: BasicStruct{
|
||||||
|
Name: "Alice",
|
||||||
|
},
|
||||||
|
Comment: "partial",
|
||||||
|
},
|
||||||
|
wantErr: false,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
var s NestedStruct
|
||||||
|
changes, err := structx.AttactToStruct(&s, tt.input)
|
||||||
|
|
||||||
|
if tt.wantErr {
|
||||||
|
if err == nil {
|
||||||
|
t.Errorf("期望错误,但得到 nil")
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("意外的错误: %v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if s.Basic.Name != tt.expected.Basic.Name ||
|
||||||
|
s.Basic.Age != tt.expected.Basic.Age ||
|
||||||
|
s.Comment != tt.expected.Comment {
|
||||||
|
t.Errorf("期望 %+v, 得到 %+v", tt.expected, s)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 验证嵌套字段的变更记录
|
||||||
|
for key := range tt.input {
|
||||||
|
if _, exists := changes[key]; !exists {
|
||||||
|
t.Errorf("缺少变更记录 for key: %s", key)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 测试指针嵌套结构体
|
||||||
|
func TestAttactToStruct_PointerNested(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
input map[string]string
|
||||||
|
expected PointerStruct
|
||||||
|
wantErr bool
|
||||||
|
}{
|
||||||
|
// {
|
||||||
|
// name: "指针嵌套结构体",
|
||||||
|
// input: map[string]string{
|
||||||
|
// "basic.name": "John",
|
||||||
|
// "basic.age": "30",
|
||||||
|
// "basic.is_active": "true",
|
||||||
|
// "enabled": "true",
|
||||||
|
// },
|
||||||
|
|
||||||
|
// expected: PointerStruct{
|
||||||
|
// Basic: &BasicStruct{
|
||||||
|
// Name: "John",
|
||||||
|
// Age: 30,
|
||||||
|
// IsActive: true,
|
||||||
|
// },
|
||||||
|
// Enabled: true,
|
||||||
|
// },
|
||||||
|
// wantErr: false,
|
||||||
|
// },
|
||||||
|
{
|
||||||
|
name: "空指针初始化",
|
||||||
|
input: map[string]string{
|
||||||
|
"basic.name": "New User",
|
||||||
|
"enabled": "true",
|
||||||
|
},
|
||||||
|
expected: PointerStruct{
|
||||||
|
Basic: &BasicStruct{
|
||||||
|
Name: "New User",
|
||||||
|
},
|
||||||
|
Enabled: true,
|
||||||
|
},
|
||||||
|
wantErr: false,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
var s PointerStruct
|
||||||
|
changes, err := structx.AttactToStruct(&s, tt.input)
|
||||||
|
|
||||||
|
_ = changes
|
||||||
|
|
||||||
|
if tt.wantErr {
|
||||||
|
if err == nil {
|
||||||
|
t.Errorf("期望错误,但得到 nil")
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("意外的错误: %v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if s.Basic == nil || s.Basic.Name != tt.expected.Basic.Name ||
|
||||||
|
s.Enabled != tt.expected.Enabled {
|
||||||
|
t.Errorf("期望 %+v, 得到 %+v", tt.expected, s)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 自定义类型测试
|
||||||
|
func TestAttactToStruct_CustomTypes(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
input map[string]string
|
||||||
|
expected CustomTypeStruct
|
||||||
|
wantErr bool
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "自定义类型转换",
|
||||||
|
input: map[string]string{
|
||||||
|
"id": "user_123",
|
||||||
|
"version": "2",
|
||||||
|
"email": "test@example.com",
|
||||||
|
},
|
||||||
|
expected: CustomTypeStruct{
|
||||||
|
ID: CustomString("user_123"),
|
||||||
|
Version: CustomInt(2),
|
||||||
|
Email: "test@example.com",
|
||||||
|
},
|
||||||
|
wantErr: false,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
var s CustomTypeStruct
|
||||||
|
changes, err := structx.AttactToStruct(&s, tt.input)
|
||||||
|
|
||||||
|
if tt.wantErr {
|
||||||
|
if err == nil {
|
||||||
|
t.Errorf("期望错误,但得到 nil")
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("意外的错误: %v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if string(s.ID) != string(tt.expected.ID) ||
|
||||||
|
int(s.Version) != int(tt.expected.Version) ||
|
||||||
|
s.Email != tt.expected.Email {
|
||||||
|
t.Errorf("期望 %+v, 得到 %+v", tt.expected, s)
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(changes) != len(tt.input) {
|
||||||
|
t.Errorf("期望 %d 变更记录, 得到 %d", len(tt.input), len(changes))
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Unmarshaler 接口测试
|
||||||
|
func TestAttactToStruct_TextUnmarshaler(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
input map[string]string
|
||||||
|
expected UnmarshalerStruct
|
||||||
|
wantErr bool
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "TextUnmarshaler 接口",
|
||||||
|
input: map[string]string{
|
||||||
|
"data": "test_data",
|
||||||
|
"name": "John",
|
||||||
|
},
|
||||||
|
expected: UnmarshalerStruct{
|
||||||
|
Data: CustomUnmarshaler("custom_test_data"),
|
||||||
|
Name: "John",
|
||||||
|
},
|
||||||
|
wantErr: false,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
var s UnmarshalerStruct
|
||||||
|
changes, err := structx.AttactToStruct(&s, tt.input)
|
||||||
|
|
||||||
|
if tt.wantErr {
|
||||||
|
if err == nil {
|
||||||
|
t.Errorf("期望错误,但得到 nil")
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("意外的错误: %v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if string(s.Data) != string(tt.expected.Data) || s.Name != tt.expected.Name {
|
||||||
|
t.Errorf("期望 Data=%s, Name=%s; 得到 Data=%s, Name=%s",
|
||||||
|
tt.expected.Data, tt.expected.Name, s.Data, s.Name)
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(changes) != len(tt.input) {
|
||||||
|
t.Errorf("期望 %d 变更记录, 得到 %d", len(tt.input), len(changes))
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 错误场景测试
|
||||||
|
func TestAttactToStruct_ErrorScenarios(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
structPtr interface{}
|
||||||
|
input map[string]string
|
||||||
|
expectedErr string
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "非指针参数",
|
||||||
|
structPtr: BasicStruct{},
|
||||||
|
input: map[string]string{"name": "test"},
|
||||||
|
expectedErr: "structxx 需要是非空指针",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "空指针",
|
||||||
|
structPtr: (*BasicStruct)(nil),
|
||||||
|
input: map[string]string{"name": "test"},
|
||||||
|
expectedErr: "需要是非空指针",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "自定义类型",
|
||||||
|
structPtr: &struct{ Data []string }{},
|
||||||
|
input: map[string]string{"Data": `["test","test2"]`},
|
||||||
|
expectedErr: "",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "无效的嵌套路径",
|
||||||
|
structPtr: &NestedStruct{},
|
||||||
|
input: map[string]string{
|
||||||
|
"nonexistent.field": "value",
|
||||||
|
"basic.name": "test",
|
||||||
|
},
|
||||||
|
expectedErr: "字段 nonexistent.field 不存在", // 应该忽略不存在的字段而不报错
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
ch, err := structx.AttactToStruct(tt.structPtr, tt.input)
|
||||||
|
fmt.Printf("变更记录:%+v %+v\n", ch,tt.structPtr)
|
||||||
|
|
||||||
|
if tt.expectedErr == "" && err != nil {
|
||||||
|
t.Errorf("不期望错误但得到: %v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if tt.expectedErr != "" {
|
||||||
|
if err == nil {
|
||||||
|
t.Errorf("期望错误包含 '%s', 但得到 nil", tt.expectedErr)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if !strings.Contains(err.Error(), tt.expectedErr) {
|
||||||
|
t.Errorf("期望错误包含 '%s', 但得到: %v", tt.expectedErr, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 变更记录验证测试
|
||||||
|
func TestAttactToStruct_ChangeInfoValidation(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
input map[string]string
|
||||||
|
expectedFields []string
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "变更记录完整性",
|
||||||
|
input: map[string]string{
|
||||||
|
"name": "New Name",
|
||||||
|
"age": "25",
|
||||||
|
"is_active": "true",
|
||||||
|
},
|
||||||
|
expectedFields: []string{"name", "age", "is_active"},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
var s BasicStruct
|
||||||
|
// 设置初始值
|
||||||
|
s.Name = "Old Name"
|
||||||
|
s.Age = 30
|
||||||
|
s.IsActive = false
|
||||||
|
|
||||||
|
changes, err := structx.AttactToStruct(&s, tt.input)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("意外的错误: %v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// 验证所有期望的字段都有变更记录
|
||||||
|
for _, field := range tt.expectedFields {
|
||||||
|
change, exists := changes[field]
|
||||||
|
if !exists {
|
||||||
|
t.Errorf("缺少字段 %s 的变更记录", field)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// 验证旧值和新值
|
||||||
|
if change.Old == "" {
|
||||||
|
t.Errorf("字段 %s 的旧值不应为空", field)
|
||||||
|
}
|
||||||
|
if change.New == "" {
|
||||||
|
t.Errorf("字段 %s 的新值不应为空", field)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 验证值类型正确
|
||||||
|
if change.Val == nil {
|
||||||
|
t.Errorf("字段 %s 的值不应为 nil", field)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 验证变更记录数量
|
||||||
|
if len(changes) != len(tt.expectedFields) {
|
||||||
|
t.Errorf("期望 %d 个变更记录, 得到 %d", len(tt.expectedFields), len(changes))
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func BenchmarkAttactToStruct(b *testing.B) {
|
||||||
|
var s BasicStruct
|
||||||
|
input := map[string]string{
|
||||||
|
"name": "benchmark",
|
||||||
|
"age": "30",
|
||||||
|
"salary": "50000.0",
|
||||||
|
"is_active": "true",
|
||||||
|
"count": "100",
|
||||||
|
}
|
||||||
|
|
||||||
|
b.ResetTimer()
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
_, err := structx.AttactToStruct(&s, input)
|
||||||
|
if err != nil {
|
||||||
|
b.Fatalf("基准测试失败: %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 性能测试
|
||||||
|
func BenchmarkAttactToStruct_Nested(b *testing.B) {
|
||||||
|
var s NestedStruct
|
||||||
|
input := map[string]string{
|
||||||
|
"basic.name": "benchmark",
|
||||||
|
"basic.age": "30",
|
||||||
|
"basic.salary": "50000.0",
|
||||||
|
"basic.is_active": "true",
|
||||||
|
"comment": "test",
|
||||||
|
"amount": "500.0",
|
||||||
|
"amount2": "250.0",
|
||||||
|
"timestamp": "2024-01-01T12:00:00Z",
|
||||||
|
}
|
||||||
|
|
||||||
|
b.ResetTimer()
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
_, err := structx.AttactToStruct(&s, input)
|
||||||
|
if err != nil {
|
||||||
|
b.Fatalf("基准测试失败: %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// AttactToStructAny 测试
|
||||||
|
func TestAttactToStructAny(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
input map[string]interface{}
|
||||||
|
expected BasicStruct
|
||||||
|
wantErr bool
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "混合类型输入",
|
||||||
|
input: map[string]interface{}{
|
||||||
|
"name": "John", // string
|
||||||
|
"age": 30, // int
|
||||||
|
"salary": 50000.50, // float64
|
||||||
|
"is_active": true, // bool
|
||||||
|
"count": uint(100), // uint
|
||||||
|
},
|
||||||
|
expected: BasicStruct{
|
||||||
|
Name: "John",
|
||||||
|
Age: 30,
|
||||||
|
Salary: 50000.50,
|
||||||
|
IsActive: true,
|
||||||
|
Count: 100,
|
||||||
|
},
|
||||||
|
wantErr: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "无法转换的类型",
|
||||||
|
input: map[string]interface{}{
|
||||||
|
"name": make(chan int), // 无法转换为字符串的类型
|
||||||
|
},
|
||||||
|
wantErr: true,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
var s BasicStruct
|
||||||
|
_, err := structx.AttactToStructAny(&s, tt.input)
|
||||||
|
|
||||||
|
if tt.wantErr {
|
||||||
|
if err == nil {
|
||||||
|
t.Errorf("期望错误,但得到 nil")
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("意外的错误: %v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if s != tt.expected {
|
||||||
|
t.Errorf("期望 %+v, 得到 %+v", tt.expected, s)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 测试指针类型嵌套结构体
|
||||||
|
type PointerNestedStruct struct {
|
||||||
|
BasicPtr *BasicStruct `json:"basic_ptr"`
|
||||||
|
Direct BasicStruct `json:"direct"`
|
||||||
|
Value string `json:"value"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestAttactToStruct_PointerNested2(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
input map[string]string
|
||||||
|
expected PointerNestedStruct
|
||||||
|
wantErr bool
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "指针类型嵌套结构体",
|
||||||
|
input: map[string]string{
|
||||||
|
"basic_ptr.name": "Pointer John",
|
||||||
|
"basic_ptr.age": "35",
|
||||||
|
"direct.name": "Direct John",
|
||||||
|
"direct.age": "25",
|
||||||
|
"value": "test",
|
||||||
|
},
|
||||||
|
expected: PointerNestedStruct{
|
||||||
|
BasicPtr: &BasicStruct{
|
||||||
|
Name: "Pointer John",
|
||||||
|
Age: 35,
|
||||||
|
},
|
||||||
|
Direct: BasicStruct{
|
||||||
|
Name: "Direct John",
|
||||||
|
Age: 25,
|
||||||
|
},
|
||||||
|
Value: "test",
|
||||||
|
},
|
||||||
|
wantErr: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "空指针初始化",
|
||||||
|
input: map[string]string{
|
||||||
|
"basic_ptr.name": "New User",
|
||||||
|
},
|
||||||
|
expected: PointerNestedStruct{
|
||||||
|
BasicPtr: &BasicStruct{
|
||||||
|
Name: "New User",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
wantErr: false,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
var s PointerNestedStruct
|
||||||
|
changes, err := structx.AttactToStruct(&s, tt.input)
|
||||||
|
|
||||||
|
if tt.wantErr {
|
||||||
|
if err == nil {
|
||||||
|
t.Errorf("期望错误,但得到 nil")
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("意外的错误: %v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// 验证指针类型字段
|
||||||
|
if s.BasicPtr == nil {
|
||||||
|
t.Error("BasicPtr 不应该为 nil")
|
||||||
|
} else if s.BasicPtr.Name != tt.expected.BasicPtr.Name {
|
||||||
|
t.Errorf("BasicPtr.Name 期望 %s, 得到 %s", tt.expected.BasicPtr.Name, s.BasicPtr.Name)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 验证直接嵌套字段
|
||||||
|
if s.Direct.Name != tt.expected.Direct.Name {
|
||||||
|
t.Errorf("Direct.Name 期望 %s, 得到 %s", tt.expected.Direct.Name, s.Direct.Name)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 验证变更记录
|
||||||
|
expectedChangeCount := len(tt.input)
|
||||||
|
if len(changes) != expectedChangeCount {
|
||||||
|
t.Errorf("期望 %d 个变更记录, 得到 %d", expectedChangeCount, len(changes))
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 测试多层指针嵌套
|
||||||
|
type MultiLevelPointerStruct struct {
|
||||||
|
Level1 *Level1Struct `json:"level1"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type Level1Struct struct {
|
||||||
|
Level2 *Level2Struct `json:"level2"`
|
||||||
|
Name string `json:"name"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type Level2Struct struct {
|
||||||
|
Level3 *Level3Struct `json:"level3"`
|
||||||
|
Value int `json:"value"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type Level3Struct struct {
|
||||||
|
FinalValue string `json:"final_value"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestAttactToStruct_MultiLevelPointer(t *testing.T) {
|
||||||
|
input := map[string]string{
|
||||||
|
"level1.name": "Top Level",
|
||||||
|
"level1.level2.value": "100",
|
||||||
|
"level1.level2.level3.final_value": "end_value",
|
||||||
|
}
|
||||||
|
|
||||||
|
var s MultiLevelPointerStruct
|
||||||
|
changes, err := structx.AttactToStruct(&s, input)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("意外的错误: %v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// 验证多层指针嵌套
|
||||||
|
if s.Level1 == nil {
|
||||||
|
t.Error("Level1 不应该为 nil")
|
||||||
|
} else if s.Level1.Name != "Top Level" {
|
||||||
|
t.Errorf("Level1.Name 期望 Top Level, 得到 %s", s.Level1.Name)
|
||||||
|
}
|
||||||
|
|
||||||
|
if s.Level1.Level2 == nil {
|
||||||
|
t.Error("Level2 不应该为 nil")
|
||||||
|
} else if s.Level1.Level2.Value != 100 {
|
||||||
|
t.Errorf("Level2.Value 期望 100, 得到 %d", s.Level1.Level2.Value)
|
||||||
|
}
|
||||||
|
|
||||||
|
if s.Level1.Level2.Level3 == nil {
|
||||||
|
t.Error("Level3 不应该为 nil")
|
||||||
|
} else if s.Level1.Level2.Level3.FinalValue != "end_value" {
|
||||||
|
t.Errorf("Level3.FinalValue 期望 end_value, 得到 %s", s.Level1.Level2.Level3.FinalValue)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 验证变更记录
|
||||||
|
if len(changes) != 3 {
|
||||||
|
t.Errorf("期望 3 个变更记录, 得到 %d", len(changes))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 测试混合指针和值类型嵌套
|
||||||
|
type MixedNestedStruct struct {
|
||||||
|
PtrField *BasicStruct `json:"ptr_field"`
|
||||||
|
ValueField BasicStruct `json:"value_field"`
|
||||||
|
Simple string `json:"simple"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestAttactToStruct_MixedNested(t *testing.T) {
|
||||||
|
input := map[string]string{
|
||||||
|
"ptr_field.name": "Pointer Name",
|
||||||
|
"ptr_field.age": "40",
|
||||||
|
"value_field.name": "Value Name",
|
||||||
|
"value_field.age": "30",
|
||||||
|
"simple": "simple_value",
|
||||||
|
}
|
||||||
|
|
||||||
|
var s MixedNestedStruct
|
||||||
|
changes, err := structx.AttactToStruct(&s, input)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("意外的错误: %v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// 验证指针字段
|
||||||
|
if s.PtrField == nil {
|
||||||
|
t.Error("PtrField 不应该为 nil")
|
||||||
|
} else {
|
||||||
|
if s.PtrField.Name != "Pointer Name" {
|
||||||
|
t.Errorf("PtrField.Name 期望 Pointer Name, 得到 %s", s.PtrField.Name)
|
||||||
|
}
|
||||||
|
if s.PtrField.Age != 40 {
|
||||||
|
t.Errorf("PtrField.Age 期望 40, 得到 %d", s.PtrField.Age)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 验证值字段
|
||||||
|
if s.ValueField.Name != "Value Name" {
|
||||||
|
t.Errorf("ValueField.Name 期望 Value Name, 得到 %s", s.ValueField.Name)
|
||||||
|
}
|
||||||
|
if s.ValueField.Age != 30 {
|
||||||
|
t.Errorf("ValueField.Age 期望 30, 得到 %d", s.ValueField.Age)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 验证简单字段
|
||||||
|
if s.Simple != "simple_value" {
|
||||||
|
t.Errorf("Simple 期望 simple_value, 得到 %s", s.Simple)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 验证变更记录
|
||||||
|
if len(changes) != 5 { // 5个字段的变更
|
||||||
|
t.Errorf("期望 5 个变更记录, 得到 %d", len(changes))
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,190 @@
|
|||||||
|
package structx
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"reflect"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
// 工具函数
|
||||||
|
|
||||||
|
func getJSONTagName(field reflect.StructField) string {
|
||||||
|
jsonTag := field.Tag.Get("json")
|
||||||
|
if jsonTag == "" || jsonTag == "-" {
|
||||||
|
return field.Name
|
||||||
|
}
|
||||||
|
return strings.Split(jsonTag, ",")[0]
|
||||||
|
}
|
||||||
|
|
||||||
|
// 是否实现了json.Unmarshaler接口
|
||||||
|
func hasUnmarshalJSON(t reflect.Type) bool {
|
||||||
|
if t.Kind() == reflect.Ptr {
|
||||||
|
t = t.Elem()
|
||||||
|
}
|
||||||
|
unmarshalerType := reflect.TypeOf((*json.Unmarshaler)(nil)).Elem()
|
||||||
|
return t.Implements(unmarshalerType) || reflect.PointerTo(t).Implements(unmarshalerType)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 是否实现了text.Unmarshaler接口
|
||||||
|
func hasUnmarshalText(t reflect.Type) bool {
|
||||||
|
if t.Kind() == reflect.Ptr {
|
||||||
|
t = t.Elem()
|
||||||
|
}
|
||||||
|
textUnmarshalerType := reflect.TypeOf((*interface {
|
||||||
|
UnmarshalText([]byte) error
|
||||||
|
})(nil)).Elem()
|
||||||
|
return t.Implements(textUnmarshalerType) || reflect.PointerTo(t).Implements(textUnmarshalerType)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 设置json.Unmarshaler接口的值
|
||||||
|
func setUnmarshalJSONValue(field reflect.Value, value interface{}) error {
|
||||||
|
jsonBytes, err := json.Marshal(value)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
var fieldAddr reflect.Value
|
||||||
|
if field.CanAddr() {
|
||||||
|
fieldAddr = field.Addr()
|
||||||
|
} else {
|
||||||
|
temp := reflect.New(field.Type())
|
||||||
|
temp.Elem().Set(field)
|
||||||
|
fieldAddr = temp
|
||||||
|
}
|
||||||
|
|
||||||
|
if unmarshaler, ok := fieldAddr.Interface().(json.Unmarshaler); ok {
|
||||||
|
return unmarshaler.UnmarshalJSON(jsonBytes)
|
||||||
|
}
|
||||||
|
return fmt.Errorf("类型未实现Unmarshaler")
|
||||||
|
}
|
||||||
|
|
||||||
|
// 设置text.Unmarshaler接口的值
|
||||||
|
func setUnmarshalTextValue(field reflect.Value, value string) error {
|
||||||
|
var fieldAddr reflect.Value
|
||||||
|
if field.CanAddr() {
|
||||||
|
fieldAddr = field.Addr()
|
||||||
|
} else {
|
||||||
|
temp := reflect.New(field.Type())
|
||||||
|
temp.Elem().Set(field)
|
||||||
|
fieldAddr = temp
|
||||||
|
}
|
||||||
|
|
||||||
|
if unmarshaler, ok := fieldAddr.Interface().(interface {
|
||||||
|
UnmarshalText([]byte) error
|
||||||
|
}); ok {
|
||||||
|
return unmarshaler.UnmarshalText([]byte(value))
|
||||||
|
}
|
||||||
|
return fmt.Errorf("类型未实现UnmarshalText")
|
||||||
|
}
|
||||||
|
|
||||||
|
// 是否是基础结构体类型
|
||||||
|
func isBasicStructType(t reflect.Type) bool {
|
||||||
|
if t.Kind() == reflect.Ptr {
|
||||||
|
t = t.Elem()
|
||||||
|
}
|
||||||
|
return basicStructTypes[t.String()]
|
||||||
|
}
|
||||||
|
|
||||||
|
// 设置基础结构体类型的值
|
||||||
|
func setBasicStructValue(field reflect.Value, value string) error {
|
||||||
|
if hasUnmarshalText(field.Type()) {
|
||||||
|
return setUnmarshalTextValue(field, value)
|
||||||
|
}
|
||||||
|
return json.Unmarshal([]byte(value), field.Addr().Interface())
|
||||||
|
}
|
||||||
|
|
||||||
|
// 设置指针类型的值
|
||||||
|
func setPointerFieldValue(field reflect.Value, value string) (interface{}, error) {
|
||||||
|
if field.Kind() != reflect.Ptr {
|
||||||
|
return nil, fmt.Errorf("期望指针类型")
|
||||||
|
}
|
||||||
|
if field.IsNil() {
|
||||||
|
field.Set(reflect.New(field.Type().Elem()))
|
||||||
|
}
|
||||||
|
|
||||||
|
return new(defaultValueSetter).SetFieldValue(field.Elem(), value)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 是否是类型别名
|
||||||
|
func isTypeAlias(t reflect.Type) bool {
|
||||||
|
if t.Kind() == reflect.Ptr {
|
||||||
|
t = t.Elem()
|
||||||
|
}
|
||||||
|
return t.PkgPath() != "" && basicKinds[t.Kind()]
|
||||||
|
}
|
||||||
|
|
||||||
|
// 设置类型别名的值
|
||||||
|
func setTypeAliasValue(field reflect.Value, value string) (interface{}, error) {
|
||||||
|
baseType := getBaseTypeFromAlias(field.Type())
|
||||||
|
if baseType == nil {
|
||||||
|
return nil, fmt.Errorf("无法获取基础类型")
|
||||||
|
}
|
||||||
|
|
||||||
|
converter, exists := typeConverters[baseType.Kind()]
|
||||||
|
if !exists {
|
||||||
|
return nil, fmt.Errorf("不支持的基础类型")
|
||||||
|
}
|
||||||
|
|
||||||
|
baseValue := reflect.New(baseType).Elem()
|
||||||
|
result, err := converter(baseValue, value)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
convertedValue, err := convertToTypeAlias(field.Type(), result)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
field.Set(reflect.ValueOf(convertedValue))
|
||||||
|
return convertedValue, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// 是否是自定义结构体类型
|
||||||
|
func isCustomStructType(t reflect.Type) bool {
|
||||||
|
if t.Kind() == reflect.Ptr {
|
||||||
|
t = t.Elem()
|
||||||
|
}
|
||||||
|
return t.PkgPath() != "" && !isBasicStructType(t) && !isTypeAlias(t) && t.Kind() == reflect.Struct
|
||||||
|
}
|
||||||
|
|
||||||
|
// 设置自定义结构体类型的值
|
||||||
|
func setCustomTypeValue(field reflect.Value, value string) (interface{}, error) {
|
||||||
|
if hasUnmarshalText(field.Type()) {
|
||||||
|
if err := setUnmarshalTextValue(field, value); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return field.Interface(), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
var jsonData interface{}
|
||||||
|
if err := json.Unmarshal([]byte(value), &jsonData); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
jsonBytes, err := json.Marshal(jsonData)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := json.Unmarshal(jsonBytes, field.Addr().Interface()); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return field.Interface(), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// 设置基础结构体元素的值
|
||||||
|
func setBasicStructElement(elemValue reflect.Value, item interface{}) error {
|
||||||
|
return setStructElement(elemValue, item)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 设置结构体元素的值
|
||||||
|
func setStructElement(elemValue reflect.Value, item interface{}) error {
|
||||||
|
jsonBytes, err := json.Marshal(item)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return json.Unmarshal(jsonBytes, elemValue.Addr().Interface())
|
||||||
|
}
|
||||||
+109
@@ -0,0 +1,109 @@
|
|||||||
|
package structx
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"reflect"
|
||||||
|
)
|
||||||
|
|
||||||
|
// ValueSetter 值设置器接口
|
||||||
|
type ValueSetter interface {
|
||||||
|
SetFieldValue(field reflect.Value, value string) (interface{}, error)
|
||||||
|
SetSliceElementValue(elemValue reflect.Value, item interface{}, elemType reflect.Type) error
|
||||||
|
}
|
||||||
|
|
||||||
|
// defaultValueSetter 默认值设置器
|
||||||
|
type defaultValueSetter struct{}
|
||||||
|
|
||||||
|
func (ds *defaultValueSetter) SetFieldValue(field reflect.Value, value string) (interface{}, error) {
|
||||||
|
fieldType := field.Type()
|
||||||
|
|
||||||
|
if hasUnmarshalJSON(fieldType) {
|
||||||
|
if err := setUnmarshalJSONValue(field, value); err != nil {
|
||||||
|
return nil, fmt.Errorf("UnmarshalJSON失败: %w", err)
|
||||||
|
}
|
||||||
|
return field.Interface(), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if hasUnmarshalText(fieldType) {
|
||||||
|
if err := setUnmarshalTextValue(field, value); err != nil {
|
||||||
|
return nil, fmt.Errorf("UnmarshalText失败: %w", err)
|
||||||
|
}
|
||||||
|
return field.Interface(), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if fieldType.Kind() == reflect.Ptr {
|
||||||
|
return setPointerFieldValue(field, value)
|
||||||
|
}
|
||||||
|
|
||||||
|
if isTypeAlias(fieldType) {
|
||||||
|
return setTypeAliasValue(field, value)
|
||||||
|
}
|
||||||
|
|
||||||
|
if isCustomStructType(fieldType) {
|
||||||
|
return setCustomTypeValue(field, value)
|
||||||
|
}
|
||||||
|
|
||||||
|
converter, exists := typeConverters[fieldType.Kind()]
|
||||||
|
if !exists {
|
||||||
|
return nil, fmt.Errorf("不支持的类型: %s", fieldType.Kind())
|
||||||
|
}
|
||||||
|
|
||||||
|
result, err := converter(field, value)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
field.Set(reflect.ValueOf(result))
|
||||||
|
return result, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ds *defaultValueSetter) SetSliceElementValue(elemValue reflect.Value, item interface{}, elemType reflect.Type) error {
|
||||||
|
if elemType.Kind() == reflect.Ptr {
|
||||||
|
if elemValue.IsNil() {
|
||||||
|
elemValue.Set(reflect.New(elemType.Elem()))
|
||||||
|
}
|
||||||
|
return ds.SetSliceElementValue(elemValue.Elem(), item, elemType.Elem())
|
||||||
|
}
|
||||||
|
|
||||||
|
if hasUnmarshalJSON(elemType) {
|
||||||
|
return setUnmarshalJSONValue(elemValue, item)
|
||||||
|
}
|
||||||
|
|
||||||
|
switch elemType.Kind() {
|
||||||
|
case reflect.String:
|
||||||
|
elemValue.SetString(convertToString(item))
|
||||||
|
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
|
||||||
|
num, err := convertToFloat64(item)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("无法转换为整型: %w", err)
|
||||||
|
}
|
||||||
|
elemValue.SetInt(int64(num))
|
||||||
|
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
|
||||||
|
num, err := convertToFloat64(item)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("无法转换为无符号整型: %w", err)
|
||||||
|
}
|
||||||
|
elemValue.SetUint(uint64(num))
|
||||||
|
case reflect.Float32, reflect.Float64:
|
||||||
|
num, err := convertToFloat64(item)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("无法转换为浮点型: %w", err)
|
||||||
|
}
|
||||||
|
elemValue.SetFloat(num)
|
||||||
|
case reflect.Bool:
|
||||||
|
if b, ok := item.(bool); ok {
|
||||||
|
elemValue.SetBool(b)
|
||||||
|
} else {
|
||||||
|
return fmt.Errorf("无法转换为布尔型")
|
||||||
|
}
|
||||||
|
case reflect.Struct:
|
||||||
|
if isBasicStructType(elemType) {
|
||||||
|
return setBasicStructElement(elemValue, item)
|
||||||
|
}
|
||||||
|
return setStructElement(elemValue, item)
|
||||||
|
default:
|
||||||
|
return fmt.Errorf("不支持的切片元素类型: %s", elemType.Kind())
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user