10 Commits

Author SHA1 Message Date
yun 614ac34883 完善数据类型转换 2026-05-27 22:02:17 +08:00
yun 3fb090a620 允许map传入不存在的字段 2025-10-31 23:28:14 +08:00
yun 77a8322aad 按功能拆分文件 2025-09-21 16:30:45 +08:00
yun e57caf3080 拆开文件夹 2025-09-21 12:06:32 +08:00
yun 0e0f076fc4 封装 2025-09-21 00:40:12 +08:00
yun 64b225f26d 优化部分实现 2025-09-21 00:30:53 +08:00
yun 898bdf7f38 优化对decimal.Decimal的支持 2025-09-20 23:13:52 +08:00
yun 02152b44bf 处理数组结构体 2025-09-20 21:17:17 +08:00
yun 0b7a1cf46d 修改结构体的转换 2025-09-20 20:28:59 +08:00
yun becba13ed9 添加any类型的值 2024-10-28 21:37:58 +08:00
12 changed files with 3324 additions and 138 deletions
+122
View File
@@ -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"`
}
+222
View File
@@ -0,0 +1,222 @@
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,
reflect.Interface: convertInterface,
}
)
// 转换为字符串
func convertToString(item interface{}) string {
switch v := item.(type) {
case string:
return v
case json.Number:
return string(v)
default:
return fmt.Sprintf("%v", item)
}
}
// 转换为数字(float64
func convertToFloat64(item interface{}) (float64, error) {
switch v := item.(type) {
case float64:
return v, nil
case json.Number:
return v.Float64()
case int:
return float64(v), nil
case int8:
return float64(v), nil
case int16:
return float64(v), nil
case int32:
return float64(v), nil
case int64:
return float64(v), nil
case uint:
return float64(v), nil
case uint8:
return float64(v), nil
case uint16:
return float64(v), nil
case uint32:
return float64(v), nil
case uint64:
return float64(v), 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) {
sliceType := field.Type()
result := reflect.New(sliceType).Elem()
if err := json.Unmarshal([]byte(value), result.Addr().Interface()); err != nil {
return nil, err
}
return result.Interface(), nil
}
func convertArray(field reflect.Value, value string) (interface{}, error) {
arrayType := field.Type()
result := reflect.New(arrayType).Elem()
if err := json.Unmarshal([]byte(value), result.Addr().Interface()); err != nil {
return nil, err
}
return result.Interface(), nil
}
func convertMap(field reflect.Value, value string) (interface{}, error) {
mapType := field.Type()
result := reflect.New(mapType).Elem()
if err := json.Unmarshal([]byte(value), result.Addr().Interface()); err != nil {
return nil, err
}
return result.Interface(), nil
}
func convertInterface(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 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
}
}
+51
View File
@@ -0,0 +1,51 @@
package structx
import (
"reflect"
)
// ChangeInfo 变更信息
type ChangeInfo struct {
Old any `json:"old"`
New any `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,
"time.Duration": 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,
}
)
+107
View File
@@ -0,0 +1,107 @@
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()
cacheMutex.Lock()
// 双重检查:避免两个goroutine同时构建
if cached, exists := typeInfoCache[t]; exists {
cacheMutex.Unlock()
return cached
}
fieldMap := make(map[string]FieldInfo)
dm.buildFieldMapRecursive(t, []int{}, fieldMap, "")
typeInfoCache[t] = fieldMap
cacheMutex.Unlock()
return fieldMap
}
// 递归构建字段映射表
func (dm *defaultFieldMapper) buildFieldMapRecursive(t reflect.Type, index []int, fieldMap map[string]FieldInfo, prefix string) {
dm.buildFieldMapRecursiveWithVisited(t, index, fieldMap, prefix, make(map[reflect.Type]bool))
}
func (dm *defaultFieldMapper) buildFieldMapRecursiveWithVisited(t reflect.Type, index []int, fieldMap map[string]FieldInfo, prefix string, stack map[reflect.Type]bool) {
if stack[t] {
return
}
stack[t] = true
defer delete(stack, t)
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 field.Anonymous && actualType.Kind() == reflect.Struct && !isBasicStructType(actualType) {
dm.buildFieldMapRecursiveWithVisited(actualType, currentIndex, fieldMap, prefix, stack)
continue
}
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.buildFieldMapRecursiveWithVisited(actualType, currentIndex, fieldMap, fullKey, stack)
}
fieldMap[fullKey] = FieldInfo{
Index: currentIndex,
Name: field.Name,
IsPtr: isPtr,
FieldType: fieldType,
}
}
}
+8 -2
View File
@@ -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
)
+16 -2
View File
@@ -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
View File
@@ -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
}
}
+250 -134
View File
@@ -1,158 +1,274 @@
package structx package structx
import ( import (
"encoding/json"
"fmt" "fmt"
"reflect" "reflect"
"strconv"
"strings" "strings"
"code.yun.ink/pkg/convx"
"github.com/spf13/cast"
) )
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
}
oldValue := field.Interface()
newValue, err := sp.valueSetter.SetFieldValue(field, mapValue)
if err != nil {
return nil, fmt.Errorf("设置字段 %s 的值失败: %w", mapKey, err)
}
changeMap[mapKey] = ChangeInfo{
Old: oldValue,
New: newValue,
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])
}
if len(index) > 1 {
// 只有中间节点才自动解引用指针
for field.Kind() == reflect.Ptr {
if field.IsNil() {
field.Set(reflect.New(field.Type().Elem()))
}
field = field.Elem()
}
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{}
decoder := json.NewDecoder(strings.NewReader(value))
decoder.UseNumber()
if err := decoder.Decode(&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{}
decoder := json.NewDecoder(strings.NewReader(value))
decoder.UseNumber()
if err := decoder.Decode(&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()) {
oldValue := field.Interface()
if err := setUnmarshalJSONValue(structValue, value); err != nil {
return nil, fmt.Errorf("设置UnmarshalJSON值失败: %w", err)
}
changeMap[parentKey] = ChangeInfo{
Old: oldValue,
New: structValue.Interface(),
Val: structValue.Interface(),
}
return changeMap, nil
}
if isBasicStructType(structValue.Type()) {
oldValue := field.Interface()
if err := setBasicStructValue(structValue, value); err != nil {
return nil, fmt.Errorf("设置基本结构体值失败: %w", err)
}
changeMap[parentKey] = ChangeInfo{
Old: oldValue,
New: structValue.Interface(),
Val: structValue.Interface(),
}
return changeMap, nil
}
return nil, fmt.Errorf("嵌套结构体值必须是有效的JSON格式")
}
+184
View File
@@ -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
}
+2014
View File
File diff suppressed because it is too large Load Diff
+206
View File
@@ -0,0 +1,206 @@
package structx
import (
"encoding/json"
"fmt"
"reflect"
"strings"
"time"
)
// 工具函数
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 {
var jsonBytes []byte
if str, ok := value.(string); ok {
// 尝试直接将字符串作为JSON解析;如果是合法JSON则直接使用
if json.Valid([]byte(str)) {
jsonBytes = []byte(str)
} else {
// 不是合法JSON,作为JSON字符串值处理(加引号)
jsonBytes = []byte(`"` + str + `"`)
}
} else {
var err 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)
}
if field.Type().String() == "time.Duration" {
d, err := time.ParseDuration(value)
if err != nil {
return err
}
field.SetInt(int64(d))
return nil
}
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()
}
if basicStructTypes[t.String()] {
return false
}
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
}
if err := json.Unmarshal([]byte(value), 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())
}
+125
View File
@@ -0,0 +1,125 @@
package structx
import (
"fmt"
"reflect"
"strconv"
)
// 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 isBasicStructType(fieldType) {
if err := setBasicStructValue(field, value); err != nil {
return nil, err
}
return field.Interface(), nil
}
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:
itemStr := convertToString(item)
intVal, err := strconv.ParseInt(itemStr, 10, elemType.Bits())
if err != nil {
return fmt.Errorf("无法转换为整型: %w", err)
}
elemValue.SetInt(intVal)
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
itemStr := convertToString(item)
uintVal, err := strconv.ParseUint(itemStr, 10, elemType.Bits())
if err != nil {
return fmt.Errorf("无法转换为无符号整型: %w", err)
}
elemValue.SetUint(uintVal)
case reflect.Float32, reflect.Float64:
itemStr := convertToString(item)
floatVal, err := strconv.ParseFloat(itemStr, elemType.Bits())
if err != nil {
return fmt.Errorf("无法转换为浮点型: %w", err)
}
elemValue.SetFloat(floatVal)
case reflect.Bool:
if b, ok := item.(bool); ok {
elemValue.SetBool(b)
} else {
itemStr := convertToString(item)
b, err := strconv.ParseBool(itemStr)
if err != nil {
return fmt.Errorf("无法转换为布尔型: %w", err)
}
elemValue.SetBool(b)
}
case reflect.Struct:
if isBasicStructType(elemType) {
return setBasicStructElement(elemValue, item)
}
return setStructElement(elemValue, item)
default:
return fmt.Errorf("不支持的切片元素类型: %s", elemType.Kind())
}
return nil
}