From 1f48635da3f1376b90f4b3addf743cfe1d51a015 Mon Sep 17 00:00:00 2001 From: what Date: Thu, 20 Aug 2026 17:28:57 +0800 Subject: [PATCH] =?UTF-8?q?fix:=20New()/Array()=20=E6=AD=A3=E7=A1=AE?= =?UTF-8?q?=E5=A4=84=E7=90=86=20Nil=20=E5=8F=8A=E5=B7=B2=E7=BB=8F=E6=98=AF?= =?UTF-8?q?=20R=20=E7=9A=84=E5=85=83=E7=B4=A0=EF=BC=8C=E4=B8=8D=E5=86=8D?= =?UTF-8?q?=20panic/=E9=87=8D=E5=A4=8D=E5=8C=85=E8=A3=85?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit New(nil) 之前会 panic,现在返回 Nil(新增的 R 空实现单例,所有操作都是 安全的空操作/零值,不用调用方每次都判空)。New([]R{...}) 和 Array() 遇到 元素本身已经是 R(包括 Nil)时直接透传,不再重新拿 reflect.Value 包一层, 避免丢失原有的空值语义或类型信息。 --- reflux.go | 13 ++++++++++++ rfx.go | 9 ++++++++- rfx_nil.go | 53 +++++++++++++++++++++++++++++++++++++++++++++++++ rfx_nil_test.go | 33 ++++++++++++++++++++++++++++++ rfx_test.go | 9 +++------ 5 files changed, 110 insertions(+), 7 deletions(-) create mode 100644 rfx_nil.go diff --git a/reflux.go b/reflux.go index 5b1c211..8e88601 100644 --- a/reflux.go +++ b/reflux.go @@ -58,6 +58,19 @@ type R interface { // 也支持 interface 类型以及部分基础类型(string/bool/float),会自动解析到实际类型 // 返回一个 R 接口实例,可用于访问和操作嵌套的字段、元素和键值对 func New(v any) R { + switch t := v.(type) { + case nil: + return Nil + case R: + return t + case []R: + return &rfx{value: reflect.ValueOf(t)} + default: + if t == valuex.Nil { + return Nil + } + } + rv, isPtr, err := normalizeInputValue(v) if err != nil { diff --git a/rfx.go b/rfx.go index 12d579a..47ddad5 100644 --- a/rfx.go +++ b/rfx.go @@ -518,7 +518,14 @@ func (r *rfx) Array() []R { result := make([]R, v.Len()) for i := 0; i < v.Len(); i++ { - result[i] = &rfx{value: v.Index(i)} + elem := v.Index(i) + if elem.Kind() == reflect.Interface && !elem.IsNil() { + if r, ok := elem.Interface().(R); ok { + result[i] = r + continue + } + } + result[i] = &rfx{value: elem} } return result } diff --git a/rfx_nil.go b/rfx_nil.go new file mode 100644 index 0000000..e0c0040 --- /dev/null +++ b/rfx_nil.go @@ -0,0 +1,53 @@ +package reflux + +import ( + "reflect" + + "git.fsdpf.net/go/reflux/valuex" +) + +// nilR 是 R 接口的空实现,所有操作均为空操作或返回零值 +type nilR struct{} + +// Nil 是全局唯一的 nilR 实例,表示不存在或空的 R 值 +var Nil R = &nilR{} + +func (n *nilR) Get(path ...string) R { return n } +func (n *nilR) Scope(p ...string) R { return n } +func (n *nilR) Set(key string, v any) R { return n } +func (n *nilR) Append(items ...any) R { return n } +func (n *nilR) Delete(p ...string) R { return n } +func (n *nilR) Exists(p ...string) bool { return false } +func (n *nilR) Array() []R { return nil } +func (n *nilR) Keys() []string { return nil } +func (n *nilR) MarshalJSON() ([]byte, error) { return []byte("null"), nil } +func (n *nilR) UnmarshalJSON([]byte) error { return nil } +func (n *nilR) Lookup(path string) (valuex.Accessor, bool) { return valuex.Nil, false } +func (n *nilR) MustLookup(path string) valuex.Accessor { return valuex.Nil } +func (n *nilR) Raw() reflect.Value { return reflect.Value{} } +func (n *nilR) Ptr() any { return nil } +func (n *nilR) Any() any { return nil } +func (n *nilR) Bool() bool { return false } +func (n *nilR) Float64() float64 { return 0 } +func (n *nilR) Float32() float32 { return 0 } +func (n *nilR) Int64() int64 { return 0 } +func (n *nilR) Int32() int32 { return 0 } +func (n *nilR) Int16() int16 { return 0 } +func (n *nilR) Int8() int8 { return 0 } +func (n *nilR) Int() int { return 0 } +func (n *nilR) Uint() uint { return 0 } +func (n *nilR) Uint64() uint64 { return 0 } +func (n *nilR) Uint32() uint32 { return 0 } +func (n *nilR) Uint16() uint16 { return 0 } +func (n *nilR) Uint8() uint8 { return 0 } +func (n *nilR) String() string { return "" } +func (n *nilR) StringMapString() map[string]string { return nil } +func (n *nilR) StringMapStringSlice() map[string][]string { return nil } +func (n *nilR) StringMapBool() map[string]bool { return nil } +func (n *nilR) StringMapInt() map[string]int { return nil } +func (n *nilR) StringMapInt64() map[string]int64 { return nil } +func (n *nilR) StringMap() map[string]any { return nil } +func (n *nilR) Slice() []any { return nil } +func (n *nilR) BoolSlice() []bool { return nil } +func (n *nilR) StringSlice() []string { return nil } +func (n *nilR) IntSlice() []int { return nil } diff --git a/rfx_nil_test.go b/rfx_nil_test.go index 449c0ee..5df72fa 100644 --- a/rfx_nil_test.go +++ b/rfx_nil_test.go @@ -4,6 +4,39 @@ import ( "testing" ) +// TestNewWithNilR 测试 New 接收包含 Nil 的 []R +func TestNewWithNilR(t *testing.T) { + t.Run("New with []R{Nil}", func(t *testing.T) { + r := New([]R{Nil}) + arr := r.Array() + if len(arr) != 1 { + t.Fatalf("Expected Array() len 1, got %d", len(arr)) + } + }) + + t.Run("Array element from []R{Nil} behaves as Nil", func(t *testing.T) { + r := New([]R{Nil}) + elem := r.Array()[0] + if elem.Exists() { + t.Error("Expected Exists() to be false for Nil element") + } + if elem.Any() != nil { + t.Errorf("Expected Any() to be nil, got %v", elem.Any()) + } + if elem.String() != "" { + t.Errorf("Expected String() to be empty, got %q", elem.String()) + } + }) + + t.Run("New with []R{Nil} Slice()", func(t *testing.T) { + r := New([]R{Nil}) + s := r.Slice() + if len(s) != 1 { + t.Fatalf("Expected Slice() len 1, got %d", len(s)) + } + }) +} + // TestSetValueWithNil 测试 setValue 方法处理 nil 值的各种情况 func TestSetValueWithNil(t *testing.T) { t.Run("Set nil to pointer field", func(t *testing.T) { diff --git a/rfx_test.go b/rfx_test.go index 2b282d2..a679f2d 100644 --- a/rfx_test.go +++ b/rfx_test.go @@ -167,13 +167,10 @@ func TestNew(t *testing.T) { }) t.Run("New with nil interface", func(t *testing.T) { - defer func() { - if r := recover(); r == nil { - t.Error("Expected panic with nil interface") - } - }() var data any - New(data) + if New(data) != Nil { + t.Error("Expected Nil for nil interface") + } }) }