fix: New()/Array() 正确处理 Nil 及已经是 R 的元素,不再 panic/重复包装
New(nil) 之前会 panic,现在返回 Nil(新增的 R 空实现单例,所有操作都是
安全的空操作/零值,不用调用方每次都判空)。New([]R{...}) 和 Array() 遇到
元素本身已经是 R(包括 Nil)时直接透传,不再重新拿 reflect.Value 包一层,
避免丢失原有的空值语义或类型信息。
This commit is contained in:
@@ -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 {
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
+53
@@ -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 }
|
||||
@@ -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) {
|
||||
|
||||
+3
-6
@@ -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")
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user