package structx_test import ( "encoding/json" "fmt" "math" "strings" "sync" "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 == nil { t.Errorf("字段 %s 的旧值不应为 nil", field) } if change.New == nil { t.Errorf("字段 %s 的新值不应为 nil", 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)) } } // ===== 新增综合测试 ===== // 全类型边界测试结构体 type AllKindsStruct struct { Str string `json:"str"` I int `json:"i"` I8 int8 `json:"i8"` I16 int16 `json:"i16"` I32 int32 `json:"i32"` I64 int64 `json:"i64"` Ui uint `json:"ui"` Ui8 uint8 `json:"ui8"` Ui16 uint16 `json:"ui16"` Ui32 uint32 `json:"ui32"` Ui64 uint64 `json:"ui64"` F32 float32 `json:"f32"` F64 float64 `json:"f64"` B bool `json:"b"` } // 测试所有基本类型及边界值 func TestAttactToStruct_AllBasicKinds(t *testing.T) { tests := []struct { name string input map[string]string wantErr bool check func(*testing.T, AllKindsStruct) }{ { name: "所有类型正常值", input: map[string]string{ "str": "hello", "i": "42", "i8": "127", "i16": "32767", "i32": "2147483647", "i64": "9223372036854775807", "ui": "42", "ui8": "255", "ui16": "65535", "ui32": "4294967295", "ui64": "18446744073709551615", "f32": "3.14", "f64": "3.141592653589793", "b": "true", }, check: func(t *testing.T, s AllKindsStruct) { if s.Str != "hello" || s.I != 42 || s.I8 != 127 || s.I16 != 32767 || s.I32 != 2147483647 || s.I64 != 9223372036854775807 { t.Errorf("int类型不匹配: %+v", s) } if s.Ui != 42 || s.Ui8 != 255 || s.Ui16 != 65535 || s.Ui32 != 4294967295 || s.Ui64 != 18446744073709551615 { t.Errorf("uint类型不匹配: %+v", s) } if math.Abs(float64(s.F32)-3.14) > 0.001 || math.Abs(s.F64-3.141592653589793) > 1e-14 { t.Errorf("float类型不匹配: %+v", s) } if s.B != true { t.Errorf("bool不匹配: %+v", s) } }, }, { name: "int8溢出错误", input: map[string]string{ "i8": "128", }, wantErr: true, }, { name: "uint8溢出错误", input: map[string]string{ "ui16": "70000", }, wantErr: true, }, { name: "负整数", input: map[string]string{ "i": "-1", "i8": "-128", "i64": "-9223372036854775808", }, check: func(t *testing.T, s AllKindsStruct) { if s.I != -1 || s.I8 != -128 || s.I64 != -9223372036854775808 { t.Errorf("负数不匹配: %+v", s) } }, }, { name: "uint负数错误", input: map[string]string{ "ui": "-1", }, wantErr: true, }, { name: "浮点数科学计数法", input: map[string]string{ "f64": "1e10", "f32": "1e10", }, check: func(t *testing.T, s AllKindsStruct) { if math.Abs(s.F64-1e10) > 1 { t.Errorf("科学计数法不匹配: %+v", s) } }, }, { name: "布尔值多种表达", input: map[string]string{ "b": "true", }, check: func(t *testing.T, s AllKindsStruct) { if s.B != true { t.Errorf("true不匹配") } }, }, { name: "字符串含特殊字符", input: map[string]string{ "str": "hello\nworld\tunicodë", }, check: func(t *testing.T, s AllKindsStruct) { if s.Str != "hello\nworld\tunicodë" { t.Errorf("特殊字符串不匹配: %q", s.Str) } }, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { var s AllKindsStruct _, err := structx.AttactToStruct(&s, tt.input) if tt.wantErr { if err == nil { t.Error("期望错误但得到nil") } return } if err != nil { t.Fatalf("意外错误: %v", err) } if tt.check != nil { tt.check(t, s) } }) } } // ===== Map 字段测试 ===== type MapStruct struct { Metadata map[string]string `json:"metadata"` Counts map[string]int `json:"counts"` Empty map[string]string `json:"empty"` } func TestAttactToStruct_Maps(t *testing.T) { tests := []struct { name string input map[string]string expected MapStruct wantErr bool }{ { name: "map[string]string", input: map[string]string{ "metadata": `{"key1":"val1","key2":"val2"}`, }, expected: MapStruct{ Metadata: map[string]string{"key1": "val1", "key2": "val2"}, }, }, { name: "map[string]int", input: map[string]string{ "counts": `{"a":1,"b":2,"c":3}`, }, expected: MapStruct{ Counts: map[string]int{"a": 1, "b": 2, "c": 3}, }, }, { name: "空map", input: map[string]string{ "metadata": `{}`, }, expected: MapStruct{ Metadata: map[string]string{}, }, }, { name: "无效JSON错误", input: map[string]string{ "metadata": `not-json`, }, wantErr: true, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { var s MapStruct _, err := structx.AttactToStruct(&s, tt.input) if tt.wantErr { if err == nil { t.Error("期望错误但得到nil") } return } if err != nil { t.Fatalf("意外错误: %v", err) } for k, v := range tt.expected.Metadata { if s.Metadata[k] != v { t.Errorf("Metadata[%s] 期望 %s, 得到 %s", k, v, s.Metadata[k]) } } for k, v := range tt.expected.Counts { if s.Counts[k] != v { t.Errorf("Counts[%s] 期望 %d, 得到 %d", k, v, s.Counts[k]) } } }) } } // ===== 切片/数组字段测试 ===== type SliceArrayStruct struct { Tags []string `json:"tags"` Scores []int64 `json:"scores"` Empty []string `json:"empty"` Floats []float64 `json:"floats"` Coords [3]int `json:"coords"` } type SliceStructElemStruct struct { Items []BasicStruct `json:"items"` PtrItems []*BasicStruct `json:"ptr_items"` Times []time.Time `json:"times"` } func TestAttactToStruct_SlicesArrays(t *testing.T) { t.Run("字符串切片", func(t *testing.T) { var s SliceArrayStruct _, err := structx.AttactToStruct(&s, map[string]string{ "tags": `["go","json","test"]`, }) if err != nil { t.Fatalf("意外错误: %v", err) } if len(s.Tags) != 3 || s.Tags[0] != "go" || s.Tags[1] != "json" || s.Tags[2] != "test" { t.Fatalf("期望 [go json test], 得到 %v", s.Tags) } }) t.Run("整数切片大数值精度", func(t *testing.T) { var s SliceArrayStruct _, err := structx.AttactToStruct(&s, map[string]string{ "scores": `[9007199254740993,9223372036854775807]`, }) if err != nil { t.Fatalf("意外错误: %v", err) } if len(s.Scores) != 2 || s.Scores[0] != 9007199254740993 || s.Scores[1] != 9223372036854775807 { t.Fatalf("大整数精度丢失: %v", s.Scores) } }) t.Run("浮点数切片", func(t *testing.T) { var s SliceArrayStruct _, err := structx.AttactToStruct(&s, map[string]string{ "floats": `[3.14,2.718,1.0]`, }) if err != nil { t.Fatalf("意外错误: %v", err) } if len(s.Floats) != 3 || math.Abs(s.Floats[0]-3.14) > 0.001 { t.Fatalf("浮点数切片不匹配: %v", s.Floats) } }) t.Run("空切片", func(t *testing.T) { var s SliceArrayStruct _, err := structx.AttactToStruct(&s, map[string]string{ "empty": `[]`, }) if err != nil { t.Fatalf("意外错误: %v", err) } if len(s.Empty) != 0 { t.Fatalf("期望空切片, 得到 %v", s.Empty) } }) t.Run("固定数组正常", func(t *testing.T) { var s SliceArrayStruct _, err := structx.AttactToStruct(&s, map[string]string{ "coords": `[1,2,3]`, }) if err != nil { t.Fatalf("意外错误: %v", err) } if s.Coords != [3]int{1, 2, 3} { t.Fatalf("期望 [1 2 3], 得到 %v", s.Coords) } }) t.Run("固定数组长度不匹配错误", func(t *testing.T) { var s SliceArrayStruct _, err := structx.AttactToStruct(&s, map[string]string{ "coords": `[1,2]`, }) if err == nil { t.Fatal("期望数组长度不匹配错误但得到nil") } }) t.Run("切片元素为结构体", func(t *testing.T) { var s SliceStructElemStruct _, err := structx.AttactToStruct(&s, map[string]string{ "items": `[{"name":"Alice","age":30,"salary":1000.5,"is_active":true,"count":1}]`, }) if err != nil { t.Fatalf("意外错误: %v", err) } if len(s.Items) != 1 || s.Items[0].Name != "Alice" || s.Items[0].Age != 30 { t.Fatalf("结构体切片不匹配: %+v", s.Items) } }) t.Run("切片元素为结构体指针", func(t *testing.T) { var s SliceStructElemStruct _, err := structx.AttactToStruct(&s, map[string]string{ "ptr_items": `[{"name":"Bob","age":25}]`, }) if err != nil { t.Fatalf("意外错误: %v", err) } if len(s.PtrItems) != 1 || s.PtrItems[0] == nil || s.PtrItems[0].Name != "Bob" { t.Fatalf("指针结构体切片不匹配: %+v", s.PtrItems) } }) t.Run("切片元素为time.Time", func(t *testing.T) { var s SliceStructElemStruct _, err := structx.AttactToStruct(&s, map[string]string{ "times": `["2024-01-01T00:00:00Z","2025-06-15T12:30:00Z"]`, }) if err != nil { t.Fatalf("意外错误: %v", err) } if len(s.Times) != 2 { t.Fatalf("期望2个时间, 得到 %d", len(s.Times)) } expected, _ := time.Parse(time.RFC3339, "2024-01-01T00:00:00Z") if !s.Times[0].Equal(expected) { t.Fatalf("时间不匹配: %v", s.Times[0]) } }) t.Run("无效JSON数组错误", func(t *testing.T) { var s SliceArrayStruct _, err := structx.AttactToStruct(&s, map[string]string{ "tags": `not-an-array`, }) if err == nil { t.Fatal("期望JSON数组解析错误但得到nil") } }) } // ===== json.Unmarshaler 接口测试 ===== type CustomJSONType struct { Value string } func (c *CustomJSONType) UnmarshalJSON(data []byte) error { var s string if err := json.Unmarshal(data, &s); err != nil { return err } c.Value = "json_" + s return nil } type JSONUnmarshalerStruct struct { Data CustomJSONType `json:"data"` } func TestAttactToStruct_JSONUnmarshaler(t *testing.T) { t.Run("json.Unmarshaler字符串值", func(t *testing.T) { var s JSONUnmarshalerStruct _, err := structx.AttactToStruct(&s, map[string]string{ "data": "hello", }) if err != nil { t.Fatalf("意外错误: %v", err) } if s.Data.Value != "json_hello" { t.Fatalf("期望 json_hello, 得到 %s", s.Data.Value) } }) t.Run("json.Unmarshaler JSON字符串值", func(t *testing.T) { var s JSONUnmarshalerStruct _, err := structx.AttactToStruct(&s, map[string]string{ "data": `"quoted"`, }) if err != nil { t.Fatalf("意外错误: %v", err) } if s.Data.Value != "json_quoted" { t.Fatalf("期望 json_quoted, 得到 %s", s.Data.Value) } }) } // ===== time.Duration 测试 ===== type DurationStruct struct { Timeout time.Duration `json:"timeout"` Delay time.Duration `json:"delay"` } func TestAttactToStruct_Duration(t *testing.T) { t.Run("Duration字符串解析", func(t *testing.T) { var s DurationStruct _, err := structx.AttactToStruct(&s, map[string]string{ "timeout": "5s", "delay": "100ms", }) if err != nil { t.Fatalf("意外错误: %v", err) } if s.Timeout != 5*time.Second { t.Fatalf("期望 5s, 得到 %v", s.Timeout) } if s.Delay != 100*time.Millisecond { t.Fatalf("期望 100ms, 得到 %v", s.Delay) } }) t.Run("Duration无效格式错误", func(t *testing.T) { var s DurationStruct _, err := structx.AttactToStruct(&s, map[string]string{ "timeout": "invalid", }) if err == nil { t.Fatal("期望Duration解析错误但得到nil") } }) } // ===== 嵌套结构体JSON对象字符串测试 ===== type JSONNestedParent struct { Basic BasicStruct `json:"basic"` Name string `json:"name"` } func TestAttactToStruct_NestedJSONObject(t *testing.T) { t.Run("JSON对象字符串赋值嵌套结构体", func(t *testing.T) { var s JSONNestedParent _, err := structx.AttactToStruct(&s, map[string]string{ "basic": `{"name":"John","age":"30","salary":"50000.5","is_active":"true","count":"5"}`, "name": "parent", }) if err != nil { t.Fatalf("意外错误: %v", err) } if s.Basic.Name != "John" || s.Basic.Age != 30 || s.Basic.Salary != 50000.5 || !s.Basic.IsActive || s.Basic.Count != 5 { t.Fatalf("嵌套结构体不匹配: %+v", s.Basic) } if s.Name != "parent" { t.Fatalf("父字段不匹配: %s", s.Name) } }) t.Run("无效JSON对象错误", func(t *testing.T) { var s JSONNestedParent _, err := structx.AttactToStruct(&s, map[string]string{ "basic": "not-json-object", }) if err == nil { t.Fatal("期望无效JSON对象错误但得到nil") } }) } // ===== 覆盖已有值测试 ===== type OverwriteStruct struct { Name string `json:"name"` Age int `json:"age"` Salary float64 `json:"salary"` Active bool `json:"active"` } func TestAttactToStruct_OverwriteValues(t *testing.T) { t.Run("覆盖已有字符串值", func(t *testing.T) { s := OverwriteStruct{Name: "OldName", Age: 10, Salary: 100.0, Active: false} changes, err := structx.AttactToStruct(&s, map[string]string{ "name": "NewName", "age": "20", }) if err != nil { t.Fatalf("意外错误: %v", err) } if s.Name != "NewName" || s.Age != 20 || s.Salary != 100.0 || s.Active != false { t.Fatalf("覆盖不匹配: %+v", s) } // 验证ChangeInfo记录旧值 if changes["name"].Old != "OldName" || changes["name"].New != "NewName" { t.Fatalf("ChangeInfo.name 不匹配: %+v", changes["name"]) } if changes["age"].Old != 10 || changes["age"].New != 20 { t.Fatalf("ChangeInfo.age 不匹配: %+v", changes["age"]) } }) t.Run("覆盖嵌套结构体", func(t *testing.T) { s := NestedStruct{ Basic: BasicStruct{Name: "Old", Age: 1, Salary: 1.0, IsActive: false, Count: 0}, } _, err := structx.AttactToStruct(&s, map[string]string{ "basic.name": "New", "basic.age": "99", }) if err != nil { t.Fatalf("意外错误: %v", err) } if s.Basic.Name != "New" || s.Basic.Age != 99 || s.Basic.Salary != 1.0 { t.Fatalf("嵌套覆盖不匹配: %+v", s.Basic) } }) } // ===== 边界与错误场景测试 ===== type SimpleStruct struct { Name string `json:"name"` } func TestAttactToStruct_EmptyEdgeCases(t *testing.T) { t.Run("空输入map", func(t *testing.T) { var s BasicStruct changes, err := structx.AttactToStruct(&s, map[string]string{}) if err != nil { t.Fatalf("空map不应错误: %v", err) } if len(changes) != 0 { t.Fatalf("空map应无变更记录, 得到 %d", len(changes)) } }) t.Run("AllowUnknownFields选项", func(t *testing.T) { s := structx.NewStructProcessor(structx.AllowUnknownFields()) var v SimpleStruct _, err := s.AttactToStruct(&v, map[string]string{ "name": "hello", "unknown_field": "ignored", "another_unknown": "ignored", }) if err != nil { t.Fatalf("AllowUnknownFields不应错误: %v", err) } if v.Name != "hello" { t.Fatalf("期望 hello, 得到 %s", v.Name) } }) t.Run("默认不允许未知字段", func(t *testing.T) { var v SimpleStruct _, err := structx.AttactToStruct(&v, map[string]string{ "name": "hello", "bad_field": "error", }) if err == nil { t.Fatal("期望未知字段错误但得到nil") } }) } // ===== 全指针字段结构体测试 ===== type AllPointerStruct struct { Name *string `json:"name"` Age *int `json:"age"` } func TestAttactToStruct_AllPointerFields(t *testing.T) { t.Run("全指针字段赋值", func(t *testing.T) { var s AllPointerStruct _, err := structx.AttactToStruct(&s, map[string]string{ "name": "Alice", "age": "30", }) if err != nil { t.Fatalf("意外错误: %v", err) } if s.Name == nil || *s.Name != "Alice" { t.Fatalf("name指针不匹配: %v", s.Name) } if s.Age == nil || *s.Age != 30 { t.Fatalf("age指针不匹配: %v", s.Age) } }) } // ===== 同一结构体类型多次出现测试 ===== type SameTypeReusedStruct struct { First BasicStruct `json:"first"` Second BasicStruct `json:"second"` } func TestAttactToStruct_SameTypeReused(t *testing.T) { var s SameTypeReusedStruct _, err := structx.AttactToStruct(&s, map[string]string{ "first.name": "First", "first.age": "10", "second.name": "Second", "second.age": "20", }) if err != nil { t.Fatalf("意外错误: %v", err) } if s.First.Name != "First" || s.First.Age != 10 { t.Fatalf("First不匹配: %+v", s.First) } if s.Second.Name != "Second" || s.Second.Age != 20 { t.Fatalf("Second不匹配: %+v", s.Second) } } // ===== ChangeInfo.Val 类型验证 ===== type TypedValStruct struct { Name string `json:"name"` Age int `json:"age"` Salary float64 `json:"salary"` Active bool `json:"active"` Count uint `json:"count"` } func TestAttactToStruct_ChangeInfo_ValTypes(t *testing.T) { var s TypedValStruct changes, err := structx.AttactToStruct(&s, map[string]string{ "name": "test", "age": "25", "salary": "1000.5", "active": "true", "count": "42", }) if err != nil { t.Fatalf("意外错误: %v", err) } typeCheck := func(field string, val interface{}, expectedType string) { if val == nil { t.Fatalf("字段 %s 的Val为nil", field) } actualType := fmt.Sprintf("%T", val) if actualType != expectedType { t.Errorf("字段 %s 的Val类型期望 %s, 得到 %s", field, expectedType, actualType) } } typeCheck("name", changes["name"].Val, "string") typeCheck("age", changes["age"].Val, "int") typeCheck("salary", changes["salary"].Val, "float64") typeCheck("active", changes["active"].Val, "bool") typeCheck("count", changes["count"].Val, "uint") } // ===== AttactToStructAny 综合测试 ===== func TestAttactToStructAny_Comprehensive(t *testing.T) { t.Run("各种类型混合输入", func(t *testing.T) { var s BasicStruct _, err := structx.AttactToStructAny(&s, map[string]interface{}{ "name": "John", "age": 30, "salary": 50000.50, "is_active": true, "count": uint(100), }) if err != nil { t.Fatalf("意外错误: %v", err) } if s.Name != "John" || s.Age != 30 || s.Salary != 50000.50 || !s.IsActive || s.Count != 100 { t.Fatalf("值不匹配: %+v", s) } }) t.Run("int8/int16等小类型", func(t *testing.T) { var s AllKindsStruct _, err := structx.AttactToStructAny(&s, map[string]interface{}{ "str": "hello", "i": 42, "i8": int8(127), "i16": int16(32767), "i32": int32(100), "i64": int64(999), "ui": uint(1), "ui8": uint8(255), "ui16": uint16(1), "ui32": uint32(1), "ui64": uint64(1), "f32": float32(1.5), "f64": float64(3.14), "b": true, }) if err != nil { t.Fatalf("意外错误: %v", err) } if s.I8 != 127 || s.I16 != 32767 || s.Ui8 != 255 { t.Fatalf("小类型不匹配: %+v", s) } }) t.Run("嵌套map输入", func(t *testing.T) { var s PointerNestedStruct _, err := structx.AttactToStructAny(&s, map[string]interface{}{ "basic_ptr.name": "FromAny", "direct.name": "DirectAny", "value": "val", }) if err != nil { t.Fatalf("意外错误: %v", err) } if s.BasicPtr == nil || s.BasicPtr.Name != "FromAny" { t.Fatalf("BasicPtr不匹配: %+v", s.BasicPtr) } if s.Direct.Name != "DirectAny" { t.Fatalf("Direct不匹配: %s", s.Direct.Name) } }) } // ===== 并发访问测试 ===== func TestAttactToStruct_ConcurrentAccess(t *testing.T) { var wg sync.WaitGroup errChan := make(chan error, 20) for i := 0; i < 10; i++ { wg.Add(1) go func(idx int) { defer wg.Done() var s BasicStruct _, err := structx.AttactToStruct(&s, map[string]string{ "name": fmt.Sprintf("goroutine_%d", idx), "age": fmt.Sprintf("%d", 20+idx), "salary": "1000.0", "is_active": "true", "count": "1", }) if err != nil { errChan <- err } }(i) } for i := 0; i < 10; i++ { wg.Add(1) go func(idx int) { defer wg.Done() var s NestedStruct _, err := structx.AttactToStruct(&s, map[string]string{ "basic.name": fmt.Sprintf("nested_%d", idx), "basic.age": fmt.Sprintf("%d", idx), "comment": "concurrent", "amount": "1.23", "amount2": "4.56", "timestamp": "2024-01-01T00:00:00Z", }) if err != nil { errChan <- err } }(i) } wg.Wait() close(errChan) for err := range errChan { t.Errorf("并发访问错误: %v", err) } } // ===== decimal.Decimal 指针字段测试 ===== func TestAttactToStruct_DecimalPointer(t *testing.T) { t.Run("*decimal.Decimal初始化与赋值", func(t *testing.T) { var s NestedStruct _, err := structx.AttactToStruct(&s, map[string]string{ "amount": "123.45", "amount2": "678.90", }) if err != nil { t.Fatalf("意外错误: %v", err) } if !s.Amount.Equal(decimal.RequireFromString("123.45")) { t.Fatalf("amount期望 123.45, 得到 %s", s.Amount) } if s.Amount2 == nil { t.Fatal("amount2不应为nil") } if !s.Amount2.Equal(decimal.RequireFromString("678.90")) { t.Fatalf("amount2期望 678.90, 得到 %s", s.Amount2) } }) } // ===== time.Time 字段测试 ===== func TestAttactToStruct_Time(t *testing.T) { t.Run("time.Time赋值", func(t *testing.T) { var s NestedStruct _, err := structx.AttactToStruct(&s, map[string]string{ "timestamp": "2024-06-15T10:30:00Z", }) if err != nil { t.Fatalf("意外错误: %v", err) } expected, _ := time.Parse(time.RFC3339, "2024-06-15T10:30:00Z") if !s.Timestamp.Equal(expected) { t.Fatalf("期望 %v, 得到 %v", expected, s.Timestamp) } }) t.Run("time.Time RFC3339Nano格式", func(t *testing.T) { var s NestedStruct _, err := structx.AttactToStruct(&s, map[string]string{ "timestamp": "2024-01-01T15:04:05.999999999Z", }) if err != nil { t.Fatalf("意外错误: %v", err) } expected, _ := time.Parse(time.RFC3339Nano, "2024-01-01T15:04:05.999999999Z") if !s.Timestamp.Equal(expected) { t.Fatalf("期望 %v, 得到 %v", expected, s.Timestamp) } }) } // ===== 自定义类型别名各基础类型全覆盖测试 ===== type CustomBool bool type CustomFloat64 float64 type CustomUint64 uint64 type FullAliasStruct struct { Str CustomString `json:"str"` I CustomInt `json:"i"` Ui64 CustomUint64 `json:"ui64"` F64 CustomFloat64 `json:"f64"` B CustomBool `json:"b"` } func TestAttactToStruct_FullAliasTypes(t *testing.T) { var s FullAliasStruct _, err := structx.AttactToStruct(&s, map[string]string{ "str": "alias", "i": "42", "ui64": "18446744073709551615", "f64": "3.14159", "b": "true", }) if err != nil { t.Fatalf("意外错误: %v", err) } if string(s.Str) != "alias" || int(s.I) != 42 || uint64(s.Ui64) != 18446744073709551615 { t.Fatalf("别名不匹配: str=%s i=%d ui64=%d", s.Str, s.I, s.Ui64) } if float64(s.F64) != 3.14159 || bool(s.B) != true { t.Fatalf("别名不匹配: f64=%f b=%v", s.F64, s.B) } } // ===== ComplexStruct 全字段覆盖测试 ===== func TestAttactToStruct_ComplexStruct(t *testing.T) { var s ComplexStruct _, err := structx.AttactToStruct(&s, map[string]string{ "basic.name": "Complex", "basic.age": "50", "nested.comment": "deep", "nested.amount": "99.99", "nested.timestamp": "2024-12-25T00:00:00Z", "custom.id": "cid_001", "custom.version": "3", "custom.email": "c@test.com", "timestamp": "2024-01-01T00:00:00Z", "metadata": `{"env":"prod","region":"us-east-1"}`, "unmarshaler": "raw_data", }) if err != nil { t.Fatalf("意外错误: %v", err) } if s.Basic.Name != "Complex" || s.Basic.Age != 50 { t.Errorf("Basic不匹配: %+v", s.Basic) } if s.Nested == nil { t.Fatal("Nested不应为nil") } if s.Nested.Comment != "deep" { t.Errorf("Nested.Comment不匹配: %s", s.Nested.Comment) } if s.Custom.ID != CustomString("cid_001") || s.Custom.Version != CustomInt(3) { t.Errorf("Custom不匹配: %+v", s.Custom) } if s.Metadata["env"] != "prod" || s.Metadata["region"] != "us-east-1" { t.Errorf("Metadata不匹配: %+v", s.Metadata) } if string(s.Unmarshaler) != "custom_raw_data" { t.Errorf("Unmarshaler不匹配: %s", s.Unmarshaler) } } // ===== processNestedStruct UseNumber 精度测试 ===== func TestAttactToStruct_NestedJSONObject_LargeNumber(t *testing.T) { // 验证嵌套JSON对象中的大整数不会因float64丢失精度 var s JSONNestedParent _, err := structx.AttactToStruct(&s, map[string]string{ "basic": `{"name":"test","age":9007199254740993}`, }) if err != nil { t.Fatalf("意外错误: %v", err) } // age是int类型,9007199254740993 > 2^53,如果经过float64会变成9007199254740992 if s.Basic.Age != 9007199254740993 { t.Fatalf("大数精度丢失: 期望 9007199254740993, 得到 %d", s.Basic.Age) } } // ===== SetSliceElementValue bool 字符串值测试 ===== type SliceBoolStruct struct { Flags []bool `json:"flags"` } func TestAttactToStruct_SliceBoolString(t *testing.T) { t.Run("布尔值字符串true/false", func(t *testing.T) { var s SliceBoolStruct _, err := structx.AttactToStruct(&s, map[string]string{ "flags": `["true","false","true"]`, }) if err != nil { t.Fatalf("意外错误: %v", err) } if len(s.Flags) != 3 || s.Flags[0] != true || s.Flags[1] != false || s.Flags[2] != true { t.Fatalf("布尔切片不匹配: %v", s.Flags) } }) t.Run("布尔值数字1/0", func(t *testing.T) { var s SliceBoolStruct _, err := structx.AttactToStruct(&s, map[string]string{ "flags": `[true,false]`, }) if err != nil { t.Fatalf("意外错误: %v", err) } if len(s.Flags) != 2 || s.Flags[0] != true || s.Flags[1] != false { t.Fatalf("布尔切片不匹配: %v", s.Flags) } }) } // ===== 嵌入式结构体测试 ===== type EmbeddedBase struct { BaseName string `json:"base_name"` BaseAge int `json:"base_age"` } type EmbeddedStruct struct { EmbeddedBase OwnField string `json:"own_field"` } func TestAttactToStruct_EmbeddedStruct(t *testing.T) { t.Run("嵌入式字段直接访问", func(t *testing.T) { var s EmbeddedStruct _, err := structx.AttactToStruct(&s, map[string]string{ "base_name": "embedded", "base_age": "25", "own_field": "own", }) if err != nil { t.Fatalf("意外错误: %v", err) } if s.BaseName != "embedded" || s.BaseAge != 25 || s.OwnField != "own" { t.Fatalf("嵌入式结构体不匹配: %+v", s) } }) t.Run("嵌入式指针结构体字段提升", func(t *testing.T) { type Inner struct { Val string `json:"val"` } type Outer struct { *Inner Name string `json:"name"` } var s Outer _, err := structx.AttactToStruct(&s, map[string]string{ "val": "inner_value", "name": "outer_name", }) if err != nil { t.Fatalf("意外错误: %v", err) } if s.Inner == nil { t.Fatal("Inner不应为nil") } if s.Val != "inner_value" || s.Name != "outer_name" { t.Fatalf("指针嵌入不匹配: Val=%s, Name=%s", s.Val, s.Name) } }) } // ===== interface{} 字段测试 ===== type InterfaceStruct struct { Data interface{} `json:"data"` Name string `json:"name"` } func TestAttactToStruct_InterfaceField(t *testing.T) { t.Run("interface{}字符串值", func(t *testing.T) { var s InterfaceStruct _, err := structx.AttactToStruct(&s, map[string]string{ "data": `"hello"`, "name": "test", }) if err != nil { t.Fatalf("意外错误: %v", err) } if s.Name != "test" { t.Fatalf("Name不匹配: %s", s.Name) } }) t.Run("interface{}数字值", func(t *testing.T) { var s InterfaceStruct _, err := structx.AttactToStruct(&s, map[string]string{ "data": "12345", "name": "num", }) if err != nil { t.Fatalf("意外错误: %v", err) } if s.Name != "num" { t.Fatalf("Name不匹配: %s", s.Name) } }) t.Run("interface{}对象值", func(t *testing.T) { var s InterfaceStruct _, err := structx.AttactToStruct(&s, map[string]string{ "data": `{"key":"value"}`, }) if err != nil { t.Fatalf("意外错误: %v", err) } result, ok := s.Data.(map[string]interface{}) if !ok { t.Fatalf("期望map[string]interface{}, 得到 %T", s.Data) } if result["key"] != "value" { t.Fatalf("data.key不匹配: %v", result["key"]) } }) } // ===== *[]Type 指针指向切片测试 ===== type PtrSliceStruct struct { Tags *[]string `json:"tags"` } func TestAttactToStruct_PtrToSlice(t *testing.T) { var s PtrSliceStruct _, err := structx.AttactToStruct(&s, map[string]string{ "tags": `["a","b","c"]`, }) if err != nil { t.Fatalf("意外错误: %v", err) } if s.Tags == nil { t.Fatal("Tags不应为nil") } if len(*s.Tags) != 3 || (*s.Tags)[0] != "a" { t.Fatalf("Tags不匹配: %v", *s.Tags) } } // ===== ChangeInfo Old/New 类型验证(any) ===== func TestAttactToStruct_ChangeInfo_OldNewTypes(t *testing.T) { s := OverwriteStruct{Name: "old", Age: 10, Salary: 1.5, Active: true} changes, err := structx.AttactToStruct(&s, map[string]string{ "name": "new", "age": "20", "salary": "2.5", "active": "false", }) if err != nil { t.Fatalf("意外错误: %v", err) } // 验证Old是原始Go类型,而非字符串 if _, ok := changes["name"].Old.(string); !ok { t.Errorf("name.Old期望string, 得到 %T", changes["name"].Old) } if _, ok := changes["age"].Old.(int); !ok { t.Errorf("age.Old期望int, 得到 %T", changes["age"].Old) } if _, ok := changes["salary"].Old.(float64); !ok { t.Errorf("salary.Old期望float64, 得到 %T", changes["salary"].Old) } if _, ok := changes["active"].Old.(bool); !ok { t.Errorf("active.Old期望bool, 得到 %T", changes["active"].Old) } } // ===== ChangeInfo Nested Old/New 验证 ===== func TestAttactToStruct_ChangeInfo_NestedOldNew(t *testing.T) { var s NestedStruct s.Basic = BasicStruct{Name: "old_name", Age: 10} changes, err := structx.AttactToStruct(&s, map[string]string{ "basic.name": "new_name", "basic.age": "99", }) if err != nil { t.Fatalf("意外错误: %v", err) } if changes["basic.name"].Old != "old_name" || changes["basic.name"].New != "new_name" { t.Errorf("basic.name Old/New 不匹配: Old=%v, New=%v", changes["basic.name"].Old, changes["basic.name"].New) } if changes["basic.age"].Old != 10 || changes["basic.age"].New != 99 { t.Errorf("basic.age Old/New 不匹配: Old=%v, New=%v", changes["basic.age"].Old, changes["basic.age"].New) } }