feat: R 接口支持 JSON 序列化和反序列化

实现功能:
- 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通信、配置持久化、数据传输、缓存、消息队列等
This commit is contained in:
what 2025-12-08 16:16:04 +08:00
parent a6a936c07c
commit f5f261e541
4 changed files with 441 additions and 83 deletions

169
README.md
View File

@ -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. 配置文件处理

View File

@ -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 实例

45
rfx.go
View File

@ -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
}

213
rfx_json_test.go Normal file
View File

@ -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())
}
}
}