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:
parent
a6a936c07c
commit
f5f261e541
169
README.md
169
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. 配置文件处理
|
||||
|
||||
97
reflux.go
97
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 实例
|
||||
|
||||
45
rfx.go
45
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
|
||||
}
|
||||
|
||||
213
rfx_json_test.go
Normal file
213
rfx_json_test.go
Normal 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())
|
||||
}
|
||||
}
|
||||
}
|
||||
Loading…
Reference in New Issue
Block a user