From f5f261e541e22c68d3f5a7a17676d846b3f97368 Mon Sep 17 00:00:00 2001 From: what Date: Mon, 8 Dec 2025 16:16:04 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20R=20=E6=8E=A5=E5=8F=A3=E6=94=AF?= =?UTF-8?q?=E6=8C=81=20JSON=20=E5=BA=8F=E5=88=97=E5=8C=96=E5=92=8C?= =?UTF-8?q?=E5=8F=8D=E5=BA=8F=E5=88=97=E5=8C=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 实现功能: - R 接口继承 json.Marshaler 和 json.Unmarshaler 接口 - MarshalJSON(): 将 R 实例序列化为 JSON 字节数组 - UnmarshalJSON(): 从 JSON 字节数组反序列化到 R 实例 核心特性: - 支持 struct、map、slice 等所有类型的 JSON 序列化 - 支持嵌套结构的序列化和反序列化 - 自动智能类型转换(如 JSON 数字 float64 -> int/int64) - 可以对嵌套字段单独序列化(如 rfx.Get("Address")) 测试覆盖: - TestJSONMarshal: 测试各种类型的序列化 - TestJSONUnmarshal: 测试各种类型的反序列化 - TestJSONRoundTrip: 测试序列化和反序列化的往返一致性 文档更新: - 在 README 特性列表中添加 JSON 序列化说明 - 新增"JSON 序列化和反序列化"章节 - 包含完整的使用示例和最佳实践 - 说明使用场景:API通信、配置持久化、数据传输、缓存、消息队列等 --- README.md | 169 +++++++++++++++++++++++++++++++++++++ reflux.go | 97 ++++----------------- rfx.go | 45 ++++++++++ rfx_json_test.go | 213 +++++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 441 insertions(+), 83 deletions(-) create mode 100644 rfx_json_test.go diff --git a/README.md b/README.md index 200ff24..b021388 100644 --- a/README.md +++ b/README.md @@ -15,6 +15,7 @@ Reflux 是一个 Go 语言包,提供了统一的接口用于访问和操作嵌 - 🔤 **大小写不敏感**: 支持使用小写字段名访问和修改结构体字段(包括 Map 键名) - 🔑 **键名提取**: Keys 方法可获取结构体或 Map 的所有键名 - 📝 **JSON 集成**: Ptr() 方法返回的指针可直接用于 json.Unmarshal(),实现动态 JSON 反序列化 +- 🎯 **JSON 序列化**: 实现 json.Marshaler 和 json.Unmarshaler 接口,支持直接序列化和反序列化 R 实例 - 🎯 **类型安全**: 使用反射但保证类型安全 - 🔥 **增强类型转换**: 支持切片和结构体之间的智能转换(如 []any -> []T, map -> struct) - 🌀 **R 接口集成**: 支持直接传入 R 接口或 []R 切片,无缝集成反射值 @@ -672,6 +673,174 @@ func main() { - 插件系统: 动态加载和更新插件配置 - 热更新: 在运行时更新应用配置而无需重启 +### 11. JSON 序列化和反序列化 + +R 接口实现了 `json.Marshaler` 和 `json.Unmarshaler` 接口,支持直接对 R 实例进行 JSON 序列化和反序列化: + +#### JSON 序列化 (MarshalJSON) + +```go +import ( + "encoding/json" + "git.fsdpf.net/go/reflux" +) + +type Person struct { + Name string + Age int + Tags []string +} + +func main() { + person := Person{ + Name: "Alice", + Age: 30, + Tags: []string{"developer", "golang"}, + } + + rfx := reflux.New(&person) + + // 直接序列化 R 实例 + data, err := json.Marshal(rfx) + if err != nil { + panic(err) + } + + fmt.Println(string(data)) + // 输出: {"Name":"Alice","Age":30,"Tags":["developer","golang"]} +} +``` + +#### JSON 反序列化 (UnmarshalJSON) + +```go +func main() { + person := Person{} + rfx := reflux.New(&person) + + jsonData := []byte(`{"Name":"Bob","Age":35,"Tags":["manager","python"]}`) + + // 直接反序列化到 R 实例 + err := json.Unmarshal(jsonData, rfx) + if err != nil { + panic(err) + } + + // 数据已更新到原始对象 + fmt.Printf("%+v\n", person) + // 输出: {Name:Bob Age:35 Tags:[manager python]} + + // 也可以通过 R 接口访问 + fmt.Println(rfx.Get("Name").String()) // 输出: Bob + fmt.Println(rfx.Get("Age").Int()) // 输出: 35 +} +``` + +#### JSON 往返转换 + +```go +func main() { + // 原始数据 + original := map[string]any{ + "name": "Charlie", + "age": 40, + "active": true, + } + + rfx1 := reflux.New(&original) + + // 序列化 + data, _ := json.Marshal(rfx1) + + // 反序列化到新对象 + result := make(map[string]any) + rfx2 := reflux.New(&result) + json.Unmarshal(data, rfx2) + + // 验证数据一致性 + fmt.Println(rfx2.Get("name").String()) // Charlie + fmt.Println(rfx2.Get("age").Float64()) // 40 (JSON 数字默认 float64) + fmt.Println(rfx2.Get("active").Bool()) // true +} +``` + +#### 嵌套结构序列化 + +```go +type Address struct { + City string + Country string +} + +type User struct { + Name string + Age int + Address Address +} + +func main() { + user := User{ + Name: "David", + Age: 45, + Address: Address{ + City: "Beijing", + Country: "China", + }, + } + + rfx := reflux.New(&user) + + // 序列化整个嵌套结构 + data, _ := json.Marshal(rfx) + fmt.Println(string(data)) + // 输出: {"Name":"David","Age":45,"Address":{"City":"Beijing","Country":"China"}} + + // 只序列化某个嵌套字段 + addressData, _ := json.Marshal(rfx.Get("Address")) + fmt.Println(string(addressData)) + // 输出: {"City":"Beijing","Country":"China"} +} +``` + +#### 智能类型转换 + +反序列化时,R 接口会自动进行类型转换: + +```go +type Config struct { + Port int + Timeout int64 + Enabled bool +} + +func main() { + config := Config{} + rfx := reflux.New(&config) + + // JSON 中的数字默认是 float64 + jsonData := []byte(`{"Port":8080,"Timeout":30,"Enabled":true}`) + json.Unmarshal(jsonData, rfx) + + // 自动转换为目标类型 + fmt.Printf("Port: %d (type: int)\n", config.Port) // 8080 + fmt.Printf("Timeout: %d (type: int64)\n", config.Timeout) // 30 + fmt.Printf("Enabled: %v (type: bool)\n", config.Enabled) // true +} +``` + +**使用场景**: +- **API 通信**: 直接序列化 R 实例发送到 HTTP API +- **配置持久化**: 将配置对象保存为 JSON 文件 +- **数据传输**: 在不同系统间传输复杂数据结构 +- **缓存系统**: 将对象序列化后存储到 Redis 等缓存 +- **消息队列**: 序列化后通过消息队列传输 + +**注意事项**: +- 序列化会调用底层 `Any()` 方法获取实际值 +- 反序列化支持智能类型转换,使用 `setValue` 方法 +- 对于 struct 类型,会按字段名匹配反序列化 +- JSON 数字默认解析为 `float64`,会自动转换为目标类型 + ## 使用场景 ### 1. 配置文件处理 diff --git a/reflux.go b/reflux.go index bf3b4be..f348a08 100644 --- a/reflux.go +++ b/reflux.go @@ -1,12 +1,21 @@ package reflux -import "reflect" +import ( + "encoding/json" + "reflect" + + "git.fsdpf.net/go/reflux/valuex" +) // R 提供了一个统一的接口,用于访问和操作嵌套的结构体字段、切片元素和映射值 type R interface { - // Get 通过路径获取嵌套字段的值,返回一个新的 R 实例 + valuex.Accessor + json.Marshaler + json.Unmarshaler + + // Get 通过路径获取嵌套字段的值,返回一个新的 T 实例 // 参数 p 为路径片段,例如 Get("user", "profile", "name") - Get(p ...string) R + Get(path ...string) R // Scope 类似 Get,但用于创建一个指定路径的作用域视图 // 后续操作将基于这个作用域进行 @@ -40,87 +49,9 @@ type R interface { // Keys 返回当前映射或结构体的所有键名 Keys() []string - // Value 返回底层的 reflect.Value + // Raw 返回底层的 reflect.Value // 用于需要直接操作反射值的场景 - Value() reflect.Value - - // Ptr 返回指向当前值的指针 - Ptr() any - - // Any 将当前值转换为 any 类型 - Any() any - - // Bool 将当前值转换为 bool 类型 - Bool() bool - - // Float64 将当前值转换为 float64 类型 - Float64() float64 - - // Float32 将当前值转换为 float32 类型 - Float32() float32 - - // Int64 将当前值转换为 int64 类型 - Int64() int64 - - // Int32 将当前值转换为 int32 类型 - Int32() int32 - - // Int16 将当前值转换为 int16 类型 - Int16() int16 - - // Int8 将当前值转换为 int8 类型 - Int8() int8 - - // Int 将当前值转换为 int 类型 - Int() int - - // Uint 将当前值转换为 uint 类型 - Uint() uint - - // Uint64 将当前值转换为 uint64 类型 - Uint64() uint64 - - // Uint32 将当前值转换为 uint32 类型 - Uint32() uint32 - - // Uint16 将当前值转换为 uint16 类型 - Uint16() uint16 - - // Uint8 将当前值转换为 uint8 类型 - Uint8() uint8 - - // String 将当前值转换为 string 类型 - String() string - - // StringMapString 将当前值转换为 map[string]string 类型 - StringMapString() map[string]string - - // StringMapStringSlice 将当前值转换为 map[string][]string 类型 - StringMapStringSlice() map[string][]string - - // StringMapBool 将当前值转换为 map[string]bool 类型 - StringMapBool() map[string]bool - - // StringMapInt 将当前值转换为 map[string]int 类型 - StringMapInt() map[string]int - - // StringMapInt64 将当前值转换为 map[string]int64 类型 - StringMapInt64() map[string]int64 - - // StringMap 将当前值转换为 map[string]any 类型 - StringMap() map[string]any - - // Slice 将当前值转换为 []any 切片 - Slice() []any - - // BoolSlice 将当前值转换为 []bool 切片 - BoolSlice() []bool - - // StringSlice 将当前值转换为 []string 切片 - StringSlice() []string - - // IntSlice 将当前值转换为 []int 切片 - IntSlice() []int + Raw() reflect.Value } // New 创建一个新的 R 实例 diff --git a/rfx.go b/rfx.go index f9d370c..5a89a03 100644 --- a/rfx.go +++ b/rfx.go @@ -889,3 +889,48 @@ func (r *rfx) IntSlice() []int { } return result } + +// MarshalJSON 实现 json.Marshaler 接口 +// 将当前值序列化为 JSON 字节数组 +func (r *rfx) MarshalJSON() ([]byte, error) { + return json.Marshal(r.Any()) +} + +// UnmarshalJSON 实现 json.Unmarshaler 接口 +// 从 JSON 字节数组反序列化到当前值 +func (r *rfx) UnmarshalJSON(data []byte) error { + // 先解析到 any 类型 + var v any + if err := json.Unmarshal(data, &v); err != nil { + return err + } + + // 获取当前值的实际类型 + target := r.value + for target.Kind() == reflect.Ptr || target.Kind() == reflect.Interface { + if target.IsNil() { + break + } + target = target.Elem() + } + + // 如果当前值无效或为 nil,创建一个新的 map[string]any + if !target.IsValid() || !target.CanSet() { + r.value = reflect.ValueOf(&v).Elem() + return nil + } + + // 尝试将解析的值设置到当前值 + newValue := reflect.ValueOf(v) + if newValue.Type().AssignableTo(target.Type()) { + target.Set(newValue) + return nil + } + + // 如果类型不匹配,尝试使用 setValue 进行转换 + if !r.setValue(target, v) { + return fmt.Errorf("rfx: failed to unmarshal JSON into type %s", target.Type()) + } + + return nil +} diff --git a/rfx_json_test.go b/rfx_json_test.go new file mode 100644 index 0000000..8ddaaa1 --- /dev/null +++ b/rfx_json_test.go @@ -0,0 +1,213 @@ +package reflux + +import ( + "encoding/json" + "testing" +) + +// TestJSONMarshal 测试 JSON 序列化 +func TestJSONMarshal(t *testing.T) { + t.Run("Marshal struct", func(t *testing.T) { + type Person struct { + Name string + Age int + } + person := Person{Name: "Alice", Age: 30} + r := New(&person) + + data, err := json.Marshal(r) + if err != nil { + t.Fatalf("Marshal failed: %v", err) + } + + expected := `{"Name":"Alice","Age":30}` + if string(data) != expected { + t.Errorf("Expected %s, got %s", expected, string(data)) + } + }) + + t.Run("Marshal map", func(t *testing.T) { + m := map[string]any{ + "name": "Bob", + "age": 25, + } + r := New(&m) + + data, err := json.Marshal(r) + if err != nil { + t.Fatalf("Marshal failed: %v", err) + } + + // 反序列化回来验证 + var result map[string]any + if err := json.Unmarshal(data, &result); err != nil { + t.Fatalf("Unmarshal verification failed: %v", err) + } + + if result["name"] != "Bob" || result["age"].(float64) != 25 { + t.Errorf("Unexpected result: %v", result) + } + }) + + t.Run("Marshal slice", func(t *testing.T) { + slice := []int{1, 2, 3, 4, 5} + r := New(&slice) + + data, err := json.Marshal(r) + if err != nil { + t.Fatalf("Marshal failed: %v", err) + } + + expected := `[1,2,3,4,5]` + if string(data) != expected { + t.Errorf("Expected %s, got %s", expected, string(data)) + } + }) + + t.Run("Marshal nested structure", func(t *testing.T) { + type Address struct { + City string + Country string + } + type Person struct { + Name string + Age int + Address Address + } + person := Person{ + Name: "Charlie", + Age: 35, + Address: Address{ + City: "Beijing", + Country: "China", + }, + } + r := New(&person) + + data, err := json.Marshal(r) + if err != nil { + t.Fatalf("Marshal failed: %v", err) + } + + // 验证可以正确反序列化 + var result Person + if err := json.Unmarshal(data, &result); err != nil { + t.Fatalf("Unmarshal verification failed: %v", err) + } + + if result.Name != "Charlie" || result.Age != 35 || + result.Address.City != "Beijing" || result.Address.Country != "China" { + t.Errorf("Unexpected result: %v", result) + } + }) +} + +// TestJSONUnmarshal 测试 JSON 反序列化 +func TestJSONUnmarshal(t *testing.T) { + t.Run("Unmarshal to struct", func(t *testing.T) { + type Person struct { + Name string + Age int + } + person := Person{} + r := New(&person) + + jsonData := `{"Name":"David","Age":40}` + if err := json.Unmarshal([]byte(jsonData), r); err != nil { + t.Fatalf("Unmarshal failed: %v", err) + } + + if r.Get("Name").String() != "David" || r.Get("Age").Int() != 40 { + t.Errorf("Unexpected values after unmarshal: Name=%s, Age=%d", + r.Get("Name").String(), r.Get("Age").Int()) + } + }) + + t.Run("Unmarshal to map", func(t *testing.T) { + m := make(map[string]any) + r := New(&m) + + jsonData := `{"name":"Eve","age":28,"active":true}` + if err := json.Unmarshal([]byte(jsonData), r); err != nil { + t.Fatalf("Unmarshal failed: %v", err) + } + + if r.Get("name").String() != "Eve" { + t.Errorf("Expected name=Eve, got %s", r.Get("name").String()) + } + if r.Get("age").Float64() != 28 { + t.Errorf("Expected age=28, got %v", r.Get("age").Float64()) + } + if !r.Get("active").Bool() { + t.Error("Expected active=true") + } + }) + + t.Run("Unmarshal to slice", func(t *testing.T) { + slice := []int{} + r := New(&slice) + + jsonData := `[10,20,30,40,50]` + if err := json.Unmarshal([]byte(jsonData), r); err != nil { + t.Fatalf("Unmarshal failed: %v", err) + } + + arr := r.Array() + if len(arr) != 5 { + t.Fatalf("Expected 5 elements, got %d", len(arr)) + } + + for i, expected := range []int{10, 20, 30, 40, 50} { + if arr[i].Int() != expected { + t.Errorf("arr[%d]: expected %d, got %d", i, expected, arr[i].Int()) + } + } + }) +} + +// TestJSONRoundTrip 测试序列化和反序列化往返 +func TestJSONRoundTrip(t *testing.T) { + type TestCase struct { + Name string + Age int + Tags []string + } + + original := TestCase{ + Name: "Test", + Age: 99, + Tags: []string{"tag1", "tag2", "tag3"}, + } + + // 序列化 + r1 := New(&original) + data, err := json.Marshal(r1) + if err != nil { + t.Fatalf("Marshal failed: %v", err) + } + + // 反序列化 + result := TestCase{} + r2 := New(&result) + if err := json.Unmarshal(data, r2); err != nil { + t.Fatalf("Unmarshal failed: %v", err) + } + + // 验证 + if r2.Get("Name").String() != "Test" { + t.Errorf("Name mismatch: got %s", r2.Get("Name").String()) + } + if r2.Get("Age").Int() != 99 { + t.Errorf("Age mismatch: got %d", r2.Get("Age").Int()) + } + + tags := r2.Get("Tags").Array() + if len(tags) != 3 { + t.Fatalf("Tags length mismatch: got %d", len(tags)) + } + for i, expected := range []string{"tag1", "tag2", "tag3"} { + if tags[i].String() != expected { + t.Errorf("Tags[%d] mismatch: expected %s, got %s", i, expected, tags[i].String()) + } + } +}