diff --git a/slice.go b/slice.go index e6a8328..c7aced9 100644 --- a/slice.go +++ b/slice.go @@ -8,27 +8,38 @@ package cast import ( "fmt" "reflect" + "slices" "strings" ) // ToSliceE casts any value to a []any type. func ToSliceE(i any) ([]any, error) { i, _ = indirect(i) - - var s []any + if i == nil { + return nil, fmt.Errorf(errorMsg, i, i, []any{}) + } switch v := i.(type) { case []any: - // TODO: use slices.Clone - return append(s, v...), nil - case []map[string]any: - for _, u := range v { - s = append(s, u) + return slices.Clone(v), nil + } + + kind := reflect.TypeOf(i).Kind() + switch kind { + case reflect.Slice, reflect.Array: + val := reflect.ValueOf(i) + if kind == reflect.Slice && val.IsNil() { + return nil, nil + } + + s := make([]any, val.Len()) + for j := 0; j < val.Len(); j++ { + s[j] = val.Index(j).Interface() } return s, nil default: - return s, fmt.Errorf(errorMsg, i, i, s) + return nil, fmt.Errorf(errorMsg, i, i, []any{}) } } diff --git a/slice_test.go b/slice_test.go index fb7597c..46bf8d0 100644 --- a/slice_test.go +++ b/slice_test.go @@ -230,13 +230,68 @@ func TestUintSlice(t *testing.T) { } func TestSlice(t *testing.T) { + type user struct { + Name string + Age int + } + type users []user + type customIntSlice []int + type customArray [3]int + + u1 := user{Name: "Alice", Age: 30} + u2 := user{Name: "Bob", Age: 25} + testCases := []testCase{ {[]any{1, 3}, []any{1, 3}, false}, + {[]any{}, []any{}, false}, + {[]any(nil), []any(nil), false}, {[]map[string]any{{"k1": 1}, {"k2": 2}}, []any{map[string]any{"k1": 1}, map[string]any{"k2": 2}}, false}, + // Primitives + {[]int{1, 2, 3}, []any{1, 2, 3}, false}, + {[]int8{int8(1), int8(2)}, []any{int8(1), int8(2)}, false}, + {[]int16{int16(1), int16(2)}, []any{int16(1), int16(2)}, false}, + {[]int32{int32(1), int32(2)}, []any{int32(1), int32(2)}, false}, + {[]int64{int64(1), int64(2)}, []any{int64(1), int64(2)}, false}, + {[]uint{uint(1), uint(2)}, []any{uint(1), uint(2)}, false}, + {[]uint8{uint8(1), uint8(2)}, []any{uint8(1), uint8(2)}, false}, + {[]uint16{uint16(1), uint16(2)}, []any{uint16(1), uint16(2)}, false}, + {[]uint32{uint32(1), uint32(2)}, []any{uint32(1), uint32(2)}, false}, + {[]uint64{uint64(1), uint64(2)}, []any{uint64(1), uint64(2)}, false}, + {[]float32{float32(1.5), float32(2.5)}, []any{float32(1.5), float32(2.5)}, false}, + {[]float64{1.5, 2.5}, []any{1.5, 2.5}, false}, + {[]bool{true, false}, []any{true, false}, false}, + {[]string{"foo", "bar"}, []any{"foo", "bar"}, false}, + + // Structs and pointers + {[]user{u1, u2}, []any{u1, u2}, false}, + {[]*user{&u1, &u2}, []any{&u1, &u2}, false}, + + // Custom slice types + {users{u1, u2}, []any{u1, u2}, false}, + {customIntSlice{10, 20}, []any{10, 20}, false}, + + // Arrays + {[3]int{1, 2, 3}, []any{1, 2, 3}, false}, + {[2]string{"a", "b"}, []any{"a", "b"}, false}, + {[2]user{u1, u2}, []any{u1, u2}, false}, + {customArray{7, 8, 9}, []any{7, 8, 9}, false}, + {[0]int{}, []any{}, false}, + + // Typed nil and empty slices + {[]int(nil), []any(nil), false}, + {[]string(nil), []any(nil), false}, + {[]int{}, []any{}, false}, + {[]string{}, []any{}, false}, + // Failure cases {nil, nil, true}, {testing.T{}, nil, true}, + {123, nil, true}, + {"not-a-slice", nil, true}, + {true, nil, true}, + {map[string]any{"key": "value"}, nil, true}, + {u1, nil, true}, } runSliceTests(t, testCases, cast.ToSlice, cast.ToSliceE)