├── cast_setter.go ├── examples ├── go.mod ├── go.sum └── main.go ├── go.mod ├── pointer.go ├── .gitignore ├── pointer_test.go ├── logic.go ├── length.go ├── .github ├── ISSUE_TEMPLATE │ ├── feature_request.md │ └── bug_report.md ├── CONTRIBUTING.md └── workflows │ └── tests.yml ├── Makefile ├── length_test.go ├── go.sum ├── LICENSE ├── time_test.go ├── errors.go ├── bench_test.go ├── struct_walker_access.go ├── logic_test.go ├── any.go ├── time.go ├── number_test.go ├── string_test.go ├── cast_test.go ├── float.go ├── float_test.go ├── utils.go ├── utils_test.go ├── struct_walker.go ├── struct_walker_test.go ├── number.go ├── bool.go ├── map_test.go ├── string.go ├── int.go ├── slice_test.go ├── int_test.go ├── copy_test.go ├── slice.go ├── struct_test.go ├── cast.go ├── README.md ├── map.go ├── copy.go ├── struct.go └── bool_test.go /cast_setter.go: -------------------------------------------------------------------------------- 1 | package gocast 2 | 3 | import "context" 4 | 5 | // CastSetter interface from some type into the specific value 6 | type CastSetter interface { 7 | CastSet(ctx context.Context, v any) error 8 | } 9 | -------------------------------------------------------------------------------- /examples/go.mod: -------------------------------------------------------------------------------- 1 | module github.com/demdxx/gocast/v2/examples 2 | 3 | go 1.18 4 | 5 | replace github.com/demdxx/gocast/v2 => ../ 6 | 7 | require github.com/demdxx/gocast/v2 v2.0.0 8 | 9 | require golang.org/x/exp v0.0.0-20240325151524-a685a6edb6d8 // indirect 10 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/demdxx/gocast/v2 2 | 3 | go 1.18 4 | 5 | require ( 6 | github.com/stretchr/testify v1.11.1 7 | golang.org/x/exp v0.0.0-20240325151524-a685a6edb6d8 8 | ) 9 | 10 | require ( 11 | github.com/davecgh/go-spew v1.1.1 // indirect 12 | github.com/pmezard/go-difflib v1.0.0 // indirect 13 | gopkg.in/yaml.v3 v3.0.1 // indirect 14 | ) 15 | -------------------------------------------------------------------------------- /pointer.go: -------------------------------------------------------------------------------- 1 | package gocast 2 | 3 | // PtrAsValue returns the value of `v` if `v` is not `nil`, else def 4 | // 5 | //go:inline 6 | func PtrAsValue[T any](v *T, def T) T { 7 | if v == nil { 8 | return def 9 | } 10 | return *v 11 | } 12 | 13 | // Ptr returns the pointer of v 14 | // 15 | //go:inline 16 | func Ptr[T any](v T) *T { 17 | return &v 18 | } 19 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Compiled Object files, Static and Dynamic libs (Shared Objects) 2 | *.o 3 | *.a 4 | *.so 5 | 6 | # Folders 7 | _obj 8 | _test 9 | 10 | # Architecture specific extensions/prefixes 11 | *.[568vq] 12 | [568vq].out 13 | 14 | *.cgo1.go 15 | *.cgo2.c 16 | _cgo_defun.c 17 | _cgo_gotypes.go 18 | _cgo_export.* 19 | 20 | _testmain.go 21 | 22 | *.exe 23 | *.test 24 | 25 | .DS_Store 26 | -------------------------------------------------------------------------------- /examples/go.sum: -------------------------------------------------------------------------------- 1 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= 2 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 3 | github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= 4 | golang.org/x/exp v0.0.0-20240325151524-a685a6edb6d8 h1:aAcj0Da7eBAtrTp03QXWvm88pSyOt+UgdZw2BFZ+lEw= 5 | golang.org/x/exp v0.0.0-20240325151524-a685a6edb6d8/go.mod h1:CQ1k9gNrJ50XIzaKCRR2hssIjF07kZFEiieALBM/ARQ= 6 | gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= 7 | -------------------------------------------------------------------------------- /pointer_test.go: -------------------------------------------------------------------------------- 1 | package gocast 2 | 3 | import ( 4 | "reflect" 5 | "testing" 6 | 7 | "github.com/stretchr/testify/assert" 8 | ) 9 | 10 | func TestPointer(t *testing.T) { 11 | t.Run("PtrAsValue", func(t *testing.T) { 12 | assert.Equal(t, 1, PtrAsValue(&[]int{1}[0], 2)) 13 | assert.Equal(t, 2, PtrAsValue((*int)(nil), 2)) 14 | }) 15 | 16 | t.Run("Ptr", func(t *testing.T) { 17 | v := 1 18 | assert.Equal(t, &v, Ptr(1)) 19 | assert.NotNil(t, Ptr(1)) 20 | assert.True(t, *Ptr(1) == 1) 21 | assert.True(t, reflect.TypeOf(Ptr(1)).Kind() == reflect.Pointer) 22 | }) 23 | } 24 | -------------------------------------------------------------------------------- /logic.go: -------------------------------------------------------------------------------- 1 | package gocast 2 | 3 | // Or returns the first non-empty value 4 | func Or[T any](a, b T, v ...T) T { 5 | if !IsEmpty(a) { 6 | return a 7 | } 8 | if IsEmpty(b) { 9 | for _, val := range v { 10 | if !IsEmpty(val) { 11 | return val 12 | } 13 | } 14 | } 15 | return b 16 | } 17 | 18 | // IfThen returns a if cond is true, else b 19 | func IfThen[T any](cond bool, a, b T) T { 20 | if cond { 21 | return a 22 | } 23 | return b 24 | } 25 | 26 | // IfThenExec returns a() if cond is true, else b() 27 | func IfThenExec[T any](cond bool, a func() T, b func() T) T { 28 | if cond { 29 | return a() 30 | } 31 | return b() 32 | } 33 | -------------------------------------------------------------------------------- /length.go: -------------------------------------------------------------------------------- 1 | package gocast 2 | 3 | import "reflect" 4 | 5 | // Len returns size of slice, array or map 6 | func Len[T any](val T) int { 7 | switch s := any(val).(type) { 8 | case string: 9 | return len(s) 10 | case []byte: 11 | return len(s) 12 | case []any: 13 | return len(s) 14 | case []string: 15 | return len(s) 16 | case []int: 17 | return len(s) 18 | case map[string]any: 19 | return len(s) 20 | case map[string]string: 21 | return len(s) 22 | default: 23 | obj := reflectTarget(reflect.ValueOf(val)) 24 | if obj.Kind() == reflect.Array || obj.Kind() == reflect.Slice || obj.Kind() == reflect.Map { 25 | return obj.Len() 26 | } 27 | } 28 | return -1 29 | } 30 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea for this project 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | --- 8 | 9 | **Is your feature request related to a problem? Please describe.** 10 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] 11 | 12 | **Describe the solution you'd like** 13 | A clear and concise description of what you want to happen. 14 | 15 | **Describe alternatives you've considered** 16 | A clear and concise description of any alternative solutions or features you've considered. 17 | 18 | **Additional context** 19 | Add any other context or screenshots about the feature request here. 20 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | --- 8 | 9 | **Describe the bug** 10 | A clear and concise description of what the bug is. 11 | 12 | **To Reproduce** 13 | Steps to reproduce the behavior: 14 | 15 | **Expected behavior** 16 | A clear and concise description of what you expected to happen. 17 | 18 | **Screenshots** 19 | If applicable, add screenshots to help explain your problem. 20 | 21 | **Environments** 22 | e.g Kuberenetes, AWS, GCP, Data centers, laptop, etc. 23 | 24 | **Additional context** 25 | Add any other context about the problem here--environment configurations, logs, etc. 26 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | .PHONY: fmt 2 | fmt: ## Run formatting code 3 | @echo "Fix formatting" 4 | @gofmt -w ${GO_FMT_FLAGS} $$(go list -f "{{ .Dir }}" ./...); if [ "$${errors}" != "" ]; then echo "$${errors}"; fi 5 | 6 | .PHONY: test 7 | test: ## Run tests 8 | go test -race ./... 9 | 10 | .PHONY: bench 11 | bench: ## Run benchmarks 12 | go test -benchmem -v -race -bench=. 13 | 14 | .PHONY: lint 15 | lint: ## Run linter 16 | golangci-lint run -v ./... 17 | 18 | .PHONY: tidy 19 | tidy: ## Run go mod tidy 20 | go mod tidy 21 | 22 | .PHONY: help 23 | help: 24 | @grep -E '^[a-zA-Z_-]+:.*?## .*$$' Makefile | sort | awk 'BEGIN {FS = ":.*?## "}; {printf "\033[36m%-30s\033[0m %s\n", $$1, $$2}' 25 | 26 | .DEFAULT_GOAL := help 27 | -------------------------------------------------------------------------------- /length_test.go: -------------------------------------------------------------------------------- 1 | package gocast 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/stretchr/testify/assert" 7 | ) 8 | 9 | func TestLen(t *testing.T) { 10 | assert.Equal(t, 0, Len([]any{})) 11 | assert.Equal(t, 2, Len([]any{1, "2"})) 12 | assert.Equal(t, 3, Len([]string{"1", "2", "3"})) 13 | assert.Equal(t, 3, Len("123")) 14 | assert.Equal(t, 4, Len([]int{1, 2, 3, 4})) 15 | assert.Equal(t, 4, Len([]float64{1, 2, 3, 4})) 16 | assert.Equal(t, 4, Len(&[]float64{1, 2, 3, 4})) 17 | assert.Equal(t, 3, Len([]byte("123"))) 18 | assert.Equal(t, 0, Len([]int(nil))) 19 | 20 | assert.Equal(t, 0, Len((map[string]any)(nil))) 21 | assert.Equal(t, 1, Len(map[string]any{"a": 1})) 22 | assert.Equal(t, 1, Len(map[string]string{"a": "1"})) 23 | assert.Equal(t, 1, Len(map[any]any{"a": "1"})) 24 | assert.Equal(t, 1, Len(&map[any]any{"a": "1"})) 25 | 26 | assert.Equal(t, -1, Len(1)) 27 | assert.Equal(t, -1, Len(struct{}{})) 28 | assert.Equal(t, -1, Len[any](nil)) 29 | } 30 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= 2 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 3 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 4 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 5 | github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= 6 | github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= 7 | golang.org/x/exp v0.0.0-20240325151524-a685a6edb6d8 h1:aAcj0Da7eBAtrTp03QXWvm88pSyOt+UgdZw2BFZ+lEw= 8 | golang.org/x/exp v0.0.0-20240325151524-a685a6edb6d8/go.mod h1:CQ1k9gNrJ50XIzaKCRR2hssIjF07kZFEiieALBM/ARQ= 9 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= 10 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 11 | gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= 12 | gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 13 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2014 Dmitry Ponomarev 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /time_test.go: -------------------------------------------------------------------------------- 1 | package gocast 2 | 3 | import ( 4 | "testing" 5 | "time" 6 | ) 7 | 8 | func TestParseTime(t *testing.T) { 9 | var tests = []struct { 10 | src string 11 | target time.Time 12 | }{ 13 | {src: "2021/10/24", target: time.Date(2021, 10, 24, 0, 0, 0, 0, time.UTC)}, 14 | {src: "2021-10-24", target: time.Date(2021, 10, 24, 0, 0, 0, 0, time.UTC)}, 15 | {src: "Sun, 24 Oct 2021 00:00:00 UTC", target: time.Date(2021, 10, 24, 0, 0, 0, 0, time.UTC)}, 16 | {src: "Sun Oct 24 00:00:00 UTC 2021", target: time.Date(2021, 10, 24, 0, 0, 0, 0, time.UTC)}, 17 | {src: "Sun, 24 Oct 2021 00:00:00 UTC", target: time.Date(2021, 10, 24, 0, 0, 0, 0, time.UTC)}, 18 | } 19 | 20 | for _, test := range tests { 21 | v, err := ParseTime(test.src) 22 | if err != nil { 23 | t.Error(err) 24 | } 25 | if v != test.target { 26 | t.Errorf("target must be equal %v != %v", v, test.target) 27 | } 28 | } 29 | } 30 | 31 | func BenchmarkParseTime(b *testing.B) { 32 | values := []string{ 33 | "2021/10/24", 34 | "2021-10-24", 35 | "Sun, 24 Oct 2021 00:00:00 UTC", 36 | "Sun Oct 24 00:00:00 UTC 2021", 37 | "Sun, 24 Oct 2021 00:00:00 UTC"} 38 | for n := 0; n < b.N; n++ { 39 | _, _ = ParseTime(values[n%len(values)]) 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /errors.go: -------------------------------------------------------------------------------- 1 | package gocast 2 | 3 | import ( 4 | "errors" 5 | ) 6 | 7 | type errorWrapper struct { 8 | err error 9 | msg string 10 | } 11 | 12 | func (w *errorWrapper) Error() string { return w.msg + ": " + w.err.Error() } 13 | func (w *errorWrapper) Unwrap() error { return w.err } 14 | 15 | func wrapError(err error, msg string) error { 16 | if err == nil { 17 | return nil 18 | } 19 | return &errorWrapper{err: err, msg: msg} 20 | } 21 | 22 | // Error list... 23 | var ( 24 | ErrInvalidParams = errors.New("invalid params") 25 | ErrUnsupportedType = errors.New("unsupported destination type") 26 | ErrUnsupportedSourceType = errors.New("unsupported source type") 27 | ErrUnsettableValue = errors.New("can't set value") 28 | ErrUnsupportedNumericType = errors.New("unsupported numeric type") 29 | ErrStructFieldNameUndefined = errors.New("struct field name undefined") 30 | ErrStructFieldValueCantBeChanged = errors.New("struct field value cant be changed") 31 | ErrCopyCircularReference = errors.New("circular reference detected during copy") 32 | ErrCopyUnsupportedType = errors.New("copy: unsupported type") 33 | ErrCopyInvalidValue = errors.New("copy: invalid value") 34 | ) 35 | -------------------------------------------------------------------------------- /bench_test.go: -------------------------------------------------------------------------------- 1 | package gocast 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/stretchr/testify/assert" 7 | ) 8 | 9 | func bench1(v any) int { 10 | switch iv := v.(type) { 11 | case int: 12 | return int(iv) 13 | case int8: 14 | return int(iv) 15 | case int16: 16 | return int(iv) 17 | case int32: 18 | return int(iv) 19 | } 20 | return 0 21 | } 22 | 23 | func bench2[T Numeric](v T) int { 24 | switch iv := any(v).(type) { 25 | case int: 26 | return int(iv) 27 | case int8: 28 | return int(iv) 29 | case int16: 30 | return int(iv) 31 | case int32: 32 | return int(iv) 33 | } 34 | return 0 35 | } 36 | 37 | func BenchmarkApproachTest(b *testing.B) { 38 | var vals = []struct { 39 | source int32 40 | target int 41 | }{ 42 | {source: 1, target: 1}, 43 | {source: 10, target: 10}, 44 | {source: 100, target: 100}, 45 | } 46 | b.ReportAllocs() 47 | b.Run("bench1", func(b *testing.B) { 48 | b.RunParallel(func(p *testing.PB) { 49 | for p.Next() { 50 | for _, v := range vals { 51 | assert.Equal(b, v.target, bench1(v.source)) 52 | } 53 | } 54 | }) 55 | }) 56 | b.Run("bench2", func(b *testing.B) { 57 | b.RunParallel(func(p *testing.PB) { 58 | for p.Next() { 59 | for _, v := range vals { 60 | assert.Equal(b, v.target, bench2(v.source)) 61 | } 62 | } 63 | }) 64 | }) 65 | } 66 | -------------------------------------------------------------------------------- /struct_walker_access.go: -------------------------------------------------------------------------------- 1 | package gocast 2 | 3 | import "context" 4 | 5 | type ( 6 | structWalkerFunc func(ctx context.Context, curObj StructWalkObject, field StructWalkField, path []string) error 7 | structWalkerNameFunc func(ctx context.Context, curObj StructWalkObject, field StructWalkField, path []string) string 8 | ) 9 | 10 | // StructWalkOptions is the options for StructWalk 11 | type StructWalkOptions struct { 12 | pathTag string 13 | pathExtractor structWalkerNameFunc 14 | } 15 | 16 | // PathName returns the path name of the current field 17 | func (w *StructWalkOptions) PathName(ctx context.Context, curObj StructWalkObject, field StructWalkField, path []string) string { 18 | if w != nil && w.pathExtractor != nil { 19 | return w.pathExtractor(ctx, curObj, field, path) 20 | } 21 | if w != nil && w.pathTag != "" { 22 | return field.Tag(w.pathTag) 23 | } 24 | return field.Name() 25 | } 26 | 27 | // WalkOption is the option for StructWalk 28 | type WalkOption func(w *StructWalkOptions) 29 | 30 | // WalkWithPathExtractor sets the path extractor for StructWalk 31 | func WalkWithPathExtractor(fn structWalkerNameFunc) WalkOption { 32 | return func(w *StructWalkOptions) { 33 | w.pathExtractor = fn 34 | } 35 | } 36 | 37 | // WalkWithPathTag sets the path tag for StructWalk 38 | func WalkWithPathTag(tagName string) WalkOption { 39 | return func(w *StructWalkOptions) { 40 | w.pathTag = tagName 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /logic_test.go: -------------------------------------------------------------------------------- 1 | package gocast 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/stretchr/testify/assert" 7 | ) 8 | 9 | func TestLogic(t *testing.T) { 10 | t.Run("Or", func(t *testing.T) { 11 | assert.Equal(t, 1, Or(1, 2, 3)) 12 | assert.Equal(t, 2, Or[any](nil, 2, 3)) 13 | assert.Equal(t, 3, Or[any](nil, nil, 3)) 14 | assert.Equal(t, "a", Or[any](nil, nil, "a")) 15 | assert.Equal(t, "a", Or[any](nil, "a", nil)) 16 | assert.Equal(t, "a", Or[any]("a", nil, nil)) 17 | assert.Equal(t, "a", Or[any]("a", "b", nil)) 18 | assert.Equal(t, "a", Or[any]("a", nil, "b")) 19 | assert.Equal(t, "a", Or[any](nil, "a", "b")) 20 | 21 | assert.Equal(t, 2, Or[any](0, 2, 3)) 22 | assert.Equal(t, 3, Or[any](0, 0, 3)) 23 | assert.Equal(t, "a", Or[any]("", 0, "a")) 24 | assert.Equal(t, "a", Or[any]("", "a", 0)) 25 | assert.Equal(t, "a", Or[any]("a", 0, nil)) 26 | 27 | assert.Equal(t, 2, Or(0, 2, 3)) 28 | assert.Equal(t, "a", Or("", "a", "c")) 29 | assert.Equal(t, "a", Or("", "a", "c")) 30 | assert.Equal(t, "a", Or("a", "", "c")) 31 | }) 32 | 33 | t.Run("IfThen", func(t *testing.T) { 34 | assert.Equal(t, 1, IfThen(true, 1, 2)) 35 | assert.Equal(t, 2, IfThen(false, 1, 2)) 36 | }) 37 | 38 | t.Run("IfThenExec", func(t *testing.T) { 39 | assert.Equal(t, 1, IfThenExec(true, func() any { return 1 }, func() any { return 2 })) 40 | assert.Equal(t, 2, IfThenExec(false, func() any { return 1 }, func() any { return 2 })) 41 | }) 42 | } 43 | -------------------------------------------------------------------------------- /.github/CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing 2 | 3 |
4 | Table of Contents 5 | 6 | - [Test](#test) 7 | - [Lint](#lint) 8 | - [Release](#release) 9 | 10 |
11 | 12 | All pull requests are welcome! By participating in this project, you 13 | agree to abide by our **[code of conduct]**. 14 | 15 | [code of conduct]: https://github.com/demdxx/gocast/blob/master/.github/CODE_OF_CONDUCT.md 16 | 17 | [Fork], then clone the repository: 18 | 19 | [fork]: https://github.com/demdxx/gocast/fork 20 | 21 | ```sh 22 | # replace `` with your username 23 | git clone git@github.com:/gocast.git && cd gocast 24 | ``` 25 | 26 | Write a commit message that follows the [Conventional Commits][commit] specification. 27 | 28 | The commit message will be linted during the pre-commit Git hook. 29 | 30 | Push to your fork and [submit a pull request][pr]. 31 | 32 | [pr]: https://github.com/demdxx/gocast/compare/ 33 | 34 | At this point you're waiting on us. We like to comment on pull requests 35 | within three business days (and, typically, one business day). We may suggest 36 | changes, improvements, or alternatives. 37 | 38 | Things that will improve the chance that your pull request will be accepted: 39 | 40 | - [ ] Write tests that pass [CI]. 41 | - [ ] Write good documentation. 42 | - [ ] Write a [good commit message][commit]. 43 | 44 | [ci]: https://github.com/demdxx/gocast/actions/workflows/tests.yml 45 | [commit]: https://www.conventionalcommits.org/ 46 | 47 | ## Test 48 | 49 | Run tests with coverage: 50 | 51 | ```sh 52 | make test 53 | ``` 54 | 55 | ## Lint 56 | 57 | Lint codebase: 58 | 59 | ```sh 60 | make lint 61 | ``` 62 | 63 | ## Release 64 | 65 | Release and publish. 66 | -------------------------------------------------------------------------------- /any.go: -------------------------------------------------------------------------------- 1 | package gocast 2 | 3 | // Any is a type alias for interface{} and type converting functionally. 4 | type Any struct { 5 | v any 6 | } 7 | 8 | // Any returns a new Any instance wrapping the provided value. 9 | func (a *Any) Any() any { 10 | return a.v 11 | } 12 | 13 | // Str converts any type to string. 14 | func (a *Any) Str() string { 15 | return Str(a.v) 16 | } 17 | 18 | // Int converts any type to int. 19 | func (a *Any) Int() int { 20 | return Int(a.v) 21 | } 22 | 23 | // Int64 converts any type to int64. 24 | func (a *Any) Int64() int64 { 25 | return Int64(a.v) 26 | } 27 | 28 | // Uint converts any type to uint. 29 | func (a *Any) Uint() uint { 30 | return Uint(a.v) 31 | } 32 | 33 | // Uint64 converts any type to uint64. 34 | func (a *Any) Uint64() uint64 { 35 | return Uint64(a.v) 36 | } 37 | 38 | // Float32 converts any type to float32. 39 | func (a *Any) Float32() float32 { 40 | return Float32(a.v) 41 | } 42 | 43 | // Float64 converts any type to float64. 44 | func (a *Any) Float64() float64 { 45 | return Float64(a.v) 46 | } 47 | 48 | // Bool converts any type to bool. 49 | func (a *Any) Bool() bool { 50 | return Bool(a.v) 51 | } 52 | 53 | // Slice converts any type to []any. 54 | func (a *Any) Slice() []any { 55 | return AnySlice[any](a.v) 56 | } 57 | 58 | // IsSlice checks if the value is a slice. 59 | func (a *Any) IsSlice() bool { 60 | return IsSlice(a.v) 61 | } 62 | 63 | // IsMap checks if the value is a map. 64 | func (a *Any) IsMap() bool { 65 | return IsMap(a.v) 66 | } 67 | 68 | // IsNil checks if the value is nil. 69 | func (a *Any) IsNil() bool { 70 | return IsNil(a.v) 71 | } 72 | 73 | // IsEmpty checks if the value is empty. 74 | func (a *Any) IsEmpty() bool { 75 | return IsEmpty(a.v) 76 | } 77 | -------------------------------------------------------------------------------- /time.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2014 – 2016 Dmitry Ponomarev 2 | // 3 | // Permission is hereby granted, free of charge, to any person obtaining a copy of 4 | // this software and associated documentation files (the "Software"), to deal in 5 | // the Software without restriction, including without limitation the rights to 6 | // use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 7 | // the Software, and to permit persons to whom the Software is furnished to do so, 8 | // subject to the following conditions: 9 | // 10 | // The above copyright notice and this permission notice shall be included in all 11 | // copies or substantial portions of the Software. 12 | // 13 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 15 | // FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 16 | // COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 17 | // IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 18 | // CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 19 | 20 | package gocast 21 | 22 | import ( 23 | "time" 24 | ) 25 | 26 | var timeFormats = []string{ 27 | time.RFC1123Z, 28 | time.RFC3339Nano, 29 | time.UnixDate, 30 | time.RubyDate, 31 | time.RFC1123, 32 | time.RFC3339, 33 | time.RFC822, 34 | time.RFC850, 35 | time.RFC822Z, 36 | "2006-01-02", 37 | "2006-01-02 15:04:05", 38 | "2006/01/02", 39 | "2006/01/02 15:04:05", 40 | } 41 | 42 | // ParseTime from string 43 | func ParseTime(tm string, tmFmt ...string) (t time.Time, err error) { 44 | if len(tmFmt) == 0 { 45 | tmFmt = timeFormats 46 | } 47 | for _, f := range tmFmt { 48 | if t, err = time.Parse(f, tm); err == nil { 49 | break 50 | } 51 | } 52 | return t, err 53 | } 54 | -------------------------------------------------------------------------------- /.github/workflows/tests.yml: -------------------------------------------------------------------------------- 1 | name: Tests 2 | 3 | on: 4 | push: 5 | tags: 6 | - v* 7 | branches: 8 | - master 9 | - main 10 | pull_request: 11 | branches: 12 | - master 13 | - main 14 | 15 | jobs: 16 | lint: 17 | runs-on: ubuntu-latest 18 | steps: 19 | - name: Install Go 20 | uses: actions/setup-go@v4 21 | with: 22 | go-version: 1.18.x 23 | - name: Checkout code 24 | uses: actions/checkout@v4 25 | - name: Run linters 26 | uses: golangci/golangci-lint-action@v8 27 | with: 28 | version: latest 29 | skip-cache: true 30 | args: --fix 31 | 32 | test: 33 | needs: lint 34 | strategy: 35 | matrix: 36 | go-version: [1.18.x, 1.19.x, 1.20.x, 1.21.x, 1.22.x, 1.23.x, 1.24.x] 37 | platform: [ubuntu-latest, macos-latest, windows-latest] 38 | runs-on: ${{ matrix.platform }} 39 | steps: 40 | - name: Install Go 41 | if: success() 42 | uses: actions/setup-go@v4 43 | with: 44 | go-version: ${{ matrix.go-version }} 45 | - name: Checkout code 46 | uses: actions/checkout@v4 47 | - name: Run tests 48 | run: go test -v -covermode=count 49 | 50 | coverage: 51 | runs-on: ubuntu-latest 52 | needs: test 53 | strategy: 54 | fail-fast: false 55 | matrix: 56 | go-version: [1.18.x, 1.24.x] 57 | steps: 58 | - uses: actions/setup-go@v4 59 | with: 60 | go-version: ${{ matrix.go-version }} 61 | - uses: actions/checkout@v4 62 | - run: go test -v -coverprofile=profile.cov ./... 63 | - name: Send coverage 64 | uses: shogo82148/actions-goveralls@v1 65 | with: 66 | path-to-profile: profile.cov 67 | flag-name: Go-${{ matrix.go-version }} 68 | parallel: true 69 | 70 | # notifies that all test jobs are finished. 71 | finish: 72 | needs: coverage 73 | runs-on: ubuntu-latest 74 | steps: 75 | - uses: shogo82148/actions-goveralls@v1 76 | with: 77 | parallel-finished: true 78 | -------------------------------------------------------------------------------- /number_test.go: -------------------------------------------------------------------------------- 1 | package gocast 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/stretchr/testify/assert" 7 | ) 8 | 9 | func TestNumber(t *testing.T) { 10 | assert.Equal(t, int(101), Number[int](int(101))) 11 | assert.Equal(t, int(101), Number[int](int8(101))) 12 | assert.Equal(t, int(101), Number[int](int16(101))) 13 | assert.Equal(t, int(101), Number[int](int32(101))) 14 | assert.Equal(t, int(101), Number[int](int64(101))) 15 | assert.Equal(t, int(101), Number[int](uint(101))) 16 | assert.Equal(t, int(101), Number[int](uint8(101))) 17 | assert.Equal(t, int(101), Number[int](uint16(101))) 18 | assert.Equal(t, int(101), Number[int](uint32(101))) 19 | assert.Equal(t, int(101), Number[int](uint64(101))) 20 | assert.Equal(t, int(101), Number[int](uintptr(101))) 21 | assert.Equal(t, int(101), Number[int](float64(101.))) 22 | assert.Equal(t, int(101), Number[int](float32(101.))) 23 | assert.Equal(t, int(101), Number[int]("101")) 24 | assert.Equal(t, int(101), Number[int]("101.0")) 25 | assert.Equal(t, int(1), Number[int](true)) 26 | assert.Equal(t, int(0), Number[int](false)) 27 | assert.Equal(t, int(0), Number[int](nil)) 28 | assert.Equal(t, 2.35e-01, Number[float64]("2.35E-01")) 29 | assert.Equal(t, 2.35e-01, Number[float64]("2.35e-01")) 30 | assert.Equal(t, 2e-01, Number[float64]("2e-01")) 31 | 32 | v, err := TryNumber[int](struct{ S int }{}) 33 | assert.Equal(t, int(0), v) 34 | assert.Error(t, err) 35 | } 36 | 37 | func TestIsNumeric(t *testing.T) { 38 | trueTests := []string{ 39 | "101", "+.1", "101.0", "2.35E-01", "2.35e-01", "-2e-01", "+2e-01", "1e10", "1e+10", 40 | "0x0aB1", "0b0101", "0B0101", "0o0123", "0777", 41 | } 42 | falseTests := []string{ 43 | "1x10", "-+2e-01", "2Ee-01", "3..14", "3.1.4", "-", ".", "E", "e", "x", 44 | "0x0aB1x", "0b01b", "0o0123o", "0777x", "0x", "0b", "0o", "0x0aB1x", "0b01b", "0o0123o", "0777x", 45 | "0x01YT", "0b0101012", "0o01238", "0778", 46 | } 47 | 48 | for _, v := range trueTests { 49 | assert.True(t, IsNumericStr(v), v) 50 | } 51 | 52 | for _, v := range falseTests { 53 | assert.False(t, IsNumericStr(v), v) 54 | } 55 | 56 | assert.True(t, IsNumericOnlyStr("0123456789")) 57 | assert.False(t, IsNumericOnlyStr("0123456789abcdefABCDEF")) 58 | assert.False(t, IsNumericOnlyStr("0.1")) 59 | } 60 | -------------------------------------------------------------------------------- /string_test.go: -------------------------------------------------------------------------------- 1 | package gocast 2 | 3 | import ( 4 | "math/rand" 5 | "reflect" 6 | "testing" 7 | 8 | "github.com/stretchr/testify/assert" 9 | ) 10 | 11 | var stringTypecastTests = []struct { 12 | value any 13 | target string 14 | }{ 15 | {value: 1, target: "1"}, 16 | {value: int8(1), target: "1"}, 17 | {value: int16(1), target: "1"}, 18 | {value: int32(1), target: "1"}, 19 | {value: int64(1), target: "1"}, 20 | {value: uint(1), target: "1"}, 21 | {value: uint8(1), target: "1"}, 22 | {value: uint16(1), target: "1"}, 23 | {value: uint32(1), target: "1"}, 24 | {value: uint64(1), target: "1"}, 25 | {value: 1.1, target: "1.1"}, 26 | {value: float32(1.5), target: "1.5"}, 27 | {value: true, target: "true"}, 28 | {value: false, target: "false"}, 29 | {value: []byte(`byte`), target: "byte"}, 30 | {value: `str`, target: "str"}, 31 | {value: nil, target: ""}, 32 | } 33 | 34 | func TestToStringByReflect(t *testing.T) { 35 | for _, test := range stringTypecastTests { 36 | assert.Equal(t, ReflectStr(reflect.ValueOf(test.value)), test.target) 37 | } 38 | } 39 | 40 | func TestToString(t *testing.T) { 41 | for _, test := range stringTypecastTests { 42 | assert.Equal(t, Str(test.value), test.target) 43 | } 44 | } 45 | 46 | func TestIsStr(t *testing.T) { 47 | tests := []struct { 48 | value any 49 | target bool 50 | }{ 51 | {value: 1, target: false}, 52 | {value: nil, target: false}, 53 | {value: int8(1), target: false}, 54 | {value: int16(1), target: false}, 55 | {value: []byte("notstr"), target: false}, 56 | {value: []int8{1, 2, 3, 4, 5}, target: false}, 57 | {value: []any{'1', '2', '3'}, target: false}, 58 | {value: "str", target: true}, 59 | } 60 | for _, test := range tests { 61 | assert.Equal(t, IsStr(test.value), test.target) 62 | } 63 | } 64 | 65 | func BenchmarkToStringByReflect(b *testing.B) { 66 | b.ReportAllocs() 67 | b.RunParallel(func(pb *testing.PB) { 68 | for pb.Next() { 69 | i := rand.Intn(len(stringTypecastTests)) 70 | v := reflect.ValueOf(stringTypecastTests[i].value) 71 | _ = ReflectStr(v) 72 | } 73 | }) 74 | } 75 | 76 | func BenchmarkToString(b *testing.B) { 77 | b.ReportAllocs() 78 | b.RunParallel(func(pb *testing.PB) { 79 | for pb.Next() { 80 | i := rand.Intn(len(stringTypecastTests)) 81 | _ = Str(stringTypecastTests[i].value) 82 | } 83 | }) 84 | } 85 | -------------------------------------------------------------------------------- /cast_test.go: -------------------------------------------------------------------------------- 1 | package gocast 2 | 3 | import ( 4 | "strings" 5 | "testing" 6 | 7 | "github.com/stretchr/testify/assert" 8 | ) 9 | 10 | func TestCast(t *testing.T) { 11 | type testStructType struct { 12 | S string `json:"s"` 13 | A int `json:"a"` 14 | B bool `json:"b"` 15 | } 16 | var ( 17 | tests = []any{ 18 | int(100), int8(100), int16(100), int32(100), int64(100), 19 | uint(100), uint8(100), uint16(100), uint32(100), uint64(100), 20 | uintptr(100), float32(100.), float64(100.), 21 | "100", []byte("100"), &[]int32{100}[0], 22 | } 23 | testStruct = testStructType{S: "test", A: 1, B: true} 24 | ) 25 | 26 | t.Run("simple", func(t *testing.T) { 27 | assert.Equal(t, 1, Cast[int](true)) 28 | assert.Equal(t, int8(0), Cast[int8](false)) 29 | assert.Equal(t, int16(100), Cast[int16]("100")) 30 | assert.Equal(t, int32(100), Cast[int32]("100")) 31 | assert.Equal(t, int64(100), Cast[int64]("100")) 32 | assert.Equal(t, uint(1), Cast[uint](true)) 33 | assert.Equal(t, uint8(0), Cast[uint8](false)) 34 | assert.Equal(t, uint16(100), Cast[uint16]("100")) 35 | assert.Equal(t, uint32(100), Cast[uint32]("100")) 36 | assert.Equal(t, uint64(100), Cast[uint64]("100")) 37 | assert.Equal(t, uint64(100), Cast[uint64](&[]int32{100}[0])) 38 | assert.Equal(t, true, Cast[bool](100)) 39 | assert.Equal(t, testStructType{}, ToType(testStructType{}, nil)) 40 | }) 41 | 42 | t.Run("int", func(t *testing.T) { 43 | for _, v := range tests { 44 | assert.Equal(t, 100, Cast[int](v)) 45 | assert.Equal(t, 100, CastRecursive[int](v)) 46 | } 47 | }) 48 | 49 | t.Run("float", func(t *testing.T) { 50 | for _, v := range tests { 51 | assert.Equal(t, float32(100), Cast[float32](v)) 52 | assert.Equal(t, float32(100), CastRecursive[float32](v)) 53 | assert.Equal(t, float64(100), Cast[float64](v)) 54 | assert.Equal(t, float64(100), CastRecursive[float64](v)) 55 | } 56 | }) 57 | 58 | t.Run("string", func(t *testing.T) { 59 | for _, v := range tests { 60 | assert.Equal(t, "100", strings.TrimSuffix(Cast[string](v), ".0")) 61 | assert.Equal(t, "100", strings.TrimSuffix(CastRecursive[string](v), ".0")) 62 | } 63 | }) 64 | 65 | t.Run("struct", func(t *testing.T) { 66 | mp := Cast[map[string]any](&testStruct, "json") 67 | if assert.NotNil(t, mp) { 68 | newStruct, err := TryCast[testStructType](mp, "json") 69 | assert.NoError(t, err) 70 | assert.Equal(t, testStruct, newStruct) 71 | } 72 | }) 73 | } 74 | -------------------------------------------------------------------------------- /float.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2014 Dmitry Ponomarev 2 | // 3 | // Permission is hereby granted, free of charge, to any person obtaining a copy of 4 | // this software and associated documentation files (the "Software"), to deal in 5 | // the Software without restriction, including without limitation the rights to 6 | // use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 7 | // the Software, and to permit persons to whom the Software is furnished to do so, 8 | // subject to the following conditions: 9 | // 10 | // The above copyright notice and this permission notice shall be included in all 11 | // copies or substantial portions of the Software. 12 | // 13 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 15 | // FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 16 | // COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 17 | // IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 18 | // CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 19 | 20 | package gocast 21 | 22 | import ( 23 | "reflect" 24 | "strconv" 25 | ) 26 | 27 | // ReflectToFloat64 returns float64 from reflection 28 | func ReflectToFloat64(v reflect.Value) float64 { 29 | switch v.Kind() { 30 | case reflect.String: 31 | val, _ := strconv.ParseFloat(v.String(), 64) 32 | return val 33 | case reflect.Bool: 34 | if v.Bool() { 35 | return 1. 36 | } 37 | return 0. 38 | case reflect.Slice, reflect.Array: 39 | switch v.Type().Elem().Kind() { 40 | case reflect.Uint8: 41 | str := string(v.Interface().([]byte)) 42 | fval, _ := strconv.ParseFloat(str, 64) 43 | return fval 44 | default: 45 | } 46 | case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: 47 | return float64(v.Int()) 48 | case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr: 49 | return float64(v.Uint()) 50 | case reflect.Float32, reflect.Float64: 51 | return v.Float() 52 | } 53 | return 0. 54 | } 55 | 56 | // Float from any other basic type, float64 by default 57 | // 58 | //go:inline 59 | func Float(v any) float64 { 60 | return Number[float64](v) 61 | } 62 | 63 | // Float64 from any other basic type 64 | // 65 | //go:inline 66 | func Float64(v any) float64 { 67 | return Number[float64](v) 68 | } 69 | 70 | // Float32 from any other basic type 71 | // 72 | //go:inline 73 | func Float32(v any) float32 { 74 | return Number[float32](v) 75 | } 76 | -------------------------------------------------------------------------------- /float_test.go: -------------------------------------------------------------------------------- 1 | package gocast 2 | 3 | import ( 4 | "reflect" 5 | "testing" 6 | ) 7 | 8 | func TestToFloat(t *testing.T) { 9 | var tests = []struct { 10 | src any 11 | target float64 12 | }{ 13 | {src: 120, target: 120}, 14 | {src: int8(121), target: 121}, 15 | {src: int16(121), target: 121}, 16 | {src: int32(121), target: 121}, 17 | {src: int64(121), target: 121}, 18 | {src: uint(121), target: 121}, 19 | {src: uint8(121), target: 121}, 20 | {src: uint16(121), target: 121}, 21 | {src: uint32(121), target: 121}, 22 | {src: uint64(121), target: 121}, 23 | {src: float64(122.), target: 122}, 24 | {src: float32(122.), target: 122}, 25 | {src: "123", target: 123}, 26 | {src: "120.0", target: 120}, 27 | {src: "-120.", target: -120}, 28 | {src: []byte("125."), target: 125}, 29 | {src: []byte("125"), target: 125}, 30 | {src: true, target: 1}, 31 | {src: false, target: 0}, 32 | {src: []int{100}, target: 0}, 33 | } 34 | 35 | for _, test := range tests { 36 | if v := Float64(test.src); v != test.target { 37 | t.Errorf("target must be equal %v != %f", test.src, test.target) 38 | } 39 | if v := Float32(test.src); v != float32(test.target) { 40 | t.Errorf("target must be equal %v != %f", test.src, test.target) 41 | } 42 | if v := Float(test.src); v != test.target { 43 | t.Errorf("target must be equal %v != %f", test.src, test.target) 44 | } 45 | if v := Float(test.src); v != test.target { 46 | t.Errorf("target must be equal %v != %f", test.src, test.target) 47 | } 48 | if v := Float64(test.src); v != test.target { 49 | t.Errorf("target must be equal %v != %f", test.src, test.target) 50 | } 51 | if v := Float32(test.src); v != float32(test.target) { 52 | t.Errorf("target must be equal %v != %f", test.src, test.target) 53 | } 54 | } 55 | } 56 | 57 | func TestToFloat64ByReflect(t *testing.T) { 58 | var tests = []struct { 59 | src any 60 | target float64 61 | }{ 62 | {src: 120, target: 120}, 63 | {src: int8(121), target: 121}, 64 | {src: int16(121), target: 121}, 65 | {src: int32(121), target: 121}, 66 | {src: int64(121), target: 121}, 67 | {src: uint(121), target: 121}, 68 | {src: uint8(121), target: 121}, 69 | {src: uint16(121), target: 121}, 70 | {src: uint32(121), target: 121}, 71 | {src: uint64(121), target: 121}, 72 | {src: float64(122.), target: 122}, 73 | {src: float32(122.), target: 122}, 74 | {src: "123", target: 123}, 75 | {src: "120.0", target: 120}, 76 | {src: "-120.", target: -120}, 77 | {src: []byte("125."), target: 125}, 78 | {src: []byte("125"), target: 125}, 79 | {src: true, target: 1}, 80 | {src: false, target: 0}, 81 | {src: []int{100}, target: 0}, 82 | } 83 | 84 | for _, test := range tests { 85 | if v := ReflectToFloat64(reflect.ValueOf(test.src)); v != test.target { 86 | t.Errorf("target must be equal %v != %f", test.src, test.target) 87 | } 88 | } 89 | } 90 | 91 | func BenchmarkToFloat(b *testing.B) { 92 | values := []any{120, int64(122), "123", "120.0", "120.", []byte("125."), true, false} 93 | for n := 0; n < b.N; n++ { 94 | _ = Float(values[n%len(values)]) 95 | } 96 | } 97 | -------------------------------------------------------------------------------- /utils.go: -------------------------------------------------------------------------------- 1 | package gocast 2 | 3 | import ( 4 | "database/sql/driver" 5 | "reflect" 6 | ) 7 | 8 | // IsEmpty checks value for empty state 9 | func IsEmpty[T any](v T) bool { 10 | switch tv := any(v).(type) { 11 | case nil: 12 | return true 13 | case bool: 14 | return !tv 15 | case int: 16 | return tv == 0 17 | case int8: 18 | return tv == 0 19 | case int16: 20 | return tv == 0 21 | case int32: 22 | return tv == 0 23 | case int64: 24 | return tv == 0 25 | case uint: 26 | return tv == 0 27 | case uint8: 28 | return tv == 0 29 | case uint16: 30 | return tv == 0 31 | case uint32: 32 | return tv == 0 33 | case uint64: 34 | return tv == 0 35 | case float32: 36 | return tv == 0 37 | case float64: 38 | return tv == 0 39 | case string: 40 | return tv == "" 41 | case []int: 42 | return len(tv) == 0 43 | case []int8: 44 | return len(tv) == 0 45 | case []int16: 46 | return len(tv) == 0 47 | case []int32: 48 | return len(tv) == 0 49 | case []int64: 50 | return len(tv) == 0 51 | case []uint: 52 | return len(tv) == 0 53 | case []uint8: 54 | return len(tv) == 0 55 | case []uint16: 56 | return len(tv) == 0 57 | case []uint32: 58 | return len(tv) == 0 59 | case []uint64: 60 | return len(tv) == 0 61 | case []float32: 62 | return len(tv) == 0 63 | case []float64: 64 | return len(tv) == 0 65 | case []any: 66 | return len(tv) == 0 67 | case []bool: 68 | return len(tv) == 0 69 | case []string: 70 | return len(tv) == 0 71 | } 72 | return IsEmptyByReflection(reflect.ValueOf(v)) 73 | } 74 | 75 | // IsEmptyByReflection value 76 | func IsEmptyByReflection(v reflect.Value) bool { 77 | if !v.IsValid() { 78 | return true 79 | } 80 | switch v.Kind() { 81 | case reflect.Interface, reflect.Ptr: 82 | if v.IsNil() { 83 | return true 84 | } 85 | return IsEmptyByReflection(v.Elem()) 86 | case reflect.Bool: 87 | return !v.Bool() 88 | case reflect.Slice, reflect.Array, reflect.Map, reflect.String: 89 | return v.Len() == 0 90 | case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: 91 | return v.Int() == 0 92 | case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64: 93 | return v.Uint() == 0 94 | case reflect.Float32, reflect.Float64: 95 | return v.Float() == 0 96 | } 97 | return false 98 | } 99 | 100 | func getValue(v any) any { 101 | if v == nil { 102 | return nil 103 | } 104 | if vl, ok := v.(driver.Valuer); ok { 105 | v, _ = vl.Value() 106 | } 107 | return v 108 | } 109 | 110 | func reflectTarget(r reflect.Value) reflect.Value { 111 | if !r.IsValid() { 112 | return r 113 | } 114 | for kind := r.Kind(); kind == reflect.Ptr || kind == reflect.Interface; { 115 | if r.IsNil() { 116 | break 117 | } 118 | if r = r.Elem(); !r.IsValid() { 119 | break 120 | } 121 | kind = r.Kind() 122 | } 123 | return r 124 | } 125 | 126 | // IsNil checks if the provided value is nil. 127 | func IsNil(v any) bool { 128 | if v == nil { 129 | return true 130 | } 131 | rv := reflectTarget(reflect.ValueOf(v)) 132 | if rv.Kind() == reflect.Pointer || rv.Kind() == reflect.Interface { 133 | return rv.IsNil() 134 | } 135 | return false 136 | } 137 | -------------------------------------------------------------------------------- /utils_test.go: -------------------------------------------------------------------------------- 1 | package gocast 2 | 3 | import ( 4 | "reflect" 5 | "testing" 6 | 7 | "github.com/stretchr/testify/assert" 8 | ) 9 | 10 | func TestEmpty(t *testing.T) { 11 | var tests = []struct { 12 | src any 13 | target bool 14 | }{ 15 | {src: 120, target: false}, 16 | {src: int8(120), target: false}, 17 | {src: int16(120), target: false}, 18 | {src: int32(120), target: false}, 19 | {src: int64(120), target: false}, 20 | {src: uint(121), target: false}, 21 | {src: uint8(121), target: false}, 22 | {src: uint16(121), target: false}, 23 | {src: uint32(121), target: false}, 24 | {src: uint64(121), target: false}, 25 | {src: uintptr(121), target: false}, 26 | {src: float32(122.), target: false}, 27 | {src: float64(122.), target: false}, 28 | {src: "123", target: false}, 29 | {src: "", target: true}, 30 | {src: nil, target: true}, 31 | {src: func() *struct{ s string } { return nil }(), target: true}, 32 | {src: any(nil), target: true}, 33 | {src: any(func() *struct{ s string } { return nil }()), target: true}, 34 | {src: []byte("125."), target: false}, 35 | {src: []byte(""), target: true}, 36 | {src: true, target: false}, 37 | {src: false, target: true}, 38 | {src: []any{}, target: true}, 39 | {src: []any{1}, target: false}, 40 | {src: []int{1}, target: false}, 41 | {src: []int8{1}, target: false}, 42 | {src: []int16{1}, target: false}, 43 | {src: []int32{1}, target: false}, 44 | {src: []int64{1}, target: false}, 45 | {src: []uint{1}, target: false}, 46 | {src: []uint8{1}, target: false}, 47 | {src: []uint16{1}, target: false}, 48 | {src: []uint32{1}, target: false}, 49 | {src: []uint64{1}, target: false}, 50 | {src: []uintptr{1}, target: false}, 51 | {src: []float32{1}, target: false}, 52 | {src: []float64{1}, target: false}, 53 | {src: []bool{}, target: true}, 54 | {src: []string{}, target: true}, 55 | } 56 | 57 | t.Run("IsEmpty", func(t *testing.T) { 58 | for _, test := range tests { 59 | if v := IsEmpty(test.src); v != test.target { 60 | t.Errorf("target must be equal %v != %v", test.src, test.target) 61 | } 62 | } 63 | }) 64 | t.Run("IsEmptyByReflection", func(t *testing.T) { 65 | for _, test := range tests { 66 | if v := IsEmptyByReflection(reflect.ValueOf(test.src)); v != test.target { 67 | t.Errorf("target must be equal %v != %v", test.src, test.target) 68 | } 69 | } 70 | }) 71 | } 72 | 73 | func BenchmarkIsEmpty(b *testing.B) { 74 | values := []any{120, int64(122), "123", "120.0", "120.", []byte("125."), true, false} 75 | for n := 0; n < b.N; n++ { 76 | _ = IsEmpty(values[n%len(values)]) 77 | } 78 | } 79 | 80 | func TestIsNil(t *testing.T) { 81 | tests := []struct { 82 | src any 83 | target bool 84 | }{ 85 | {src: nil, target: true}, 86 | {src: 120, target: false}, 87 | {src: int8(120), target: false}, 88 | {src: int16(120), target: false}, 89 | {src: int32(120), target: false}, 90 | {src: int64(120), target: false}, 91 | {src: uint(121), target: false}, 92 | {src: uint8(121), target: false}, 93 | {src: uint16(121), target: false}, 94 | {src: uint32(121), target: false}, 95 | {src: uint64(121), target: false}, 96 | {src: uintptr(121), target: false}, 97 | {src: any(nil), target: true}, 98 | } 99 | 100 | for _, test := range tests { 101 | assert.Equal(t, test.target, IsNil(test.src), "IsNil failed for %T", test.src) 102 | } 103 | } 104 | -------------------------------------------------------------------------------- /struct_walker.go: -------------------------------------------------------------------------------- 1 | package gocast 2 | 3 | import ( 4 | "context" 5 | "errors" 6 | "reflect" 7 | ) 8 | 9 | var ( 10 | ErrWalkSkip = errors.New("skip field walk") 11 | ErrWalkStop = errors.New("stop field walk") 12 | ) 13 | 14 | type StructWalkObject interface { 15 | Parent() StructWalkObject 16 | RefValue() reflect.Value 17 | Struct() any 18 | } 19 | 20 | type structWalkObject struct { 21 | parent StructWalkObject 22 | strct reflect.Value 23 | } 24 | 25 | func (obj *structWalkObject) Parent() StructWalkObject { return obj.parent } 26 | func (obj *structWalkObject) RefValue() reflect.Value { return obj.strct } 27 | func (obj *structWalkObject) Struct() any { return obj.strct.Interface() } 28 | 29 | // StructWalkField is the type of the field visited by StructWalk 30 | type StructWalkField interface { 31 | Name() string 32 | Tag(name string) string 33 | IsEmpty() bool 34 | RefValue() reflect.Value 35 | Value() any 36 | SetValue(ctx context.Context, v any) error 37 | } 38 | 39 | type structWalkField struct { 40 | name string 41 | fieldVal reflect.Value 42 | fieldType reflect.StructField 43 | } 44 | 45 | func (fl *structWalkField) Name() string { 46 | return fl.name 47 | } 48 | 49 | func (fl *structWalkField) Tag(name string) string { 50 | return fl.fieldType.Tag.Get(name) 51 | } 52 | 53 | func (fl *structWalkField) IsEmpty() bool { 54 | return IsEmpty(fl.Value()) 55 | } 56 | 57 | func (fl *structWalkField) RefValue() reflect.Value { 58 | return fl.fieldVal 59 | } 60 | 61 | func (fl *structWalkField) Value() any { 62 | return fl.fieldVal.Interface() 63 | } 64 | 65 | func (fl *structWalkField) SetValue(ctx context.Context, v any) error { 66 | if !fl.fieldVal.CanSet() { 67 | return wrapError(ErrStructFieldValueCantBeChanged, fl.name) 68 | } 69 | return setFieldValueNoCastSetter(ctx, fl.fieldVal, v, true) 70 | } 71 | 72 | // StructWalk walks the struct recursively 73 | func StructWalk(ctx context.Context, v any, walker structWalkerFunc, options ...WalkOption) error { 74 | structVal := reflectTarget(reflect.ValueOf(v)) 75 | if structVal.Kind() != reflect.Struct { 76 | return wrapError(ErrUnsupportedSourceType, structVal.Type().Name()) 77 | } 78 | opt := StructWalkOptions{} 79 | for _, o := range options { 80 | o(&opt) 81 | } 82 | err := _structWalk(ctx, &structWalkObject{strct: structVal}, walker, &opt) 83 | return IfThen(errors.Is(err, ErrWalkStop), nil, err) 84 | } 85 | 86 | func _structWalk(ctx context.Context, v StructWalkObject, walker structWalkerFunc, opt *StructWalkOptions, path ...string) error { 87 | var ( 88 | err error 89 | structVal = v.RefValue() 90 | structValType = structVal.Type() 91 | ) 92 | for i := 0; i < structVal.NumField(); i++ { 93 | field := structVal.Field(i) 94 | fieldType := structValType.Field(i) 95 | if !fieldType.IsExported() { 96 | continue 97 | } 98 | fieldWrapper := structWalkField{ 99 | name: fieldType.Name, 100 | fieldVal: field, 101 | fieldType: fieldType, 102 | } 103 | if err = walker(ctx, v, &fieldWrapper, path); err == nil { 104 | if stTrg := reflectTarget(field); stTrg.Kind() == reflect.Struct { 105 | newV := structWalkObject{parent: v, strct: stTrg} 106 | pathName := opt.PathName(ctx, v, &fieldWrapper, path) 107 | err = _structWalk(ctx, &newV, walker, opt, append(path, pathName)...) 108 | } 109 | } 110 | if err != nil && !errors.Is(err, ErrWalkSkip) { 111 | return err 112 | } 113 | } 114 | return nil 115 | } 116 | -------------------------------------------------------------------------------- /struct_walker_test.go: -------------------------------------------------------------------------------- 1 | package gocast 2 | 3 | import ( 4 | "context" 5 | "os" 6 | "reflect" 7 | "strings" 8 | "testing" 9 | "time" 10 | 11 | "github.com/stretchr/testify/assert" 12 | ) 13 | 14 | func TestStructWalk(t *testing.T) { 15 | ctx := context.Background() 16 | emptyWalker := func(ctx context.Context, curObj StructWalkObject, field StructWalkField, path []string) error { 17 | return nil 18 | } 19 | t.Run("noStruct", func(t *testing.T) { 20 | err := StructWalk(ctx, 1, emptyWalker) 21 | assert.ErrorIs(t, err, ErrUnsupportedSourceType) 22 | 23 | err = StructWalk(ctx, map[string]any{}, emptyWalker) 24 | assert.ErrorIs(t, err, ErrUnsupportedSourceType) 25 | 26 | err = StructWalk(ctx, struct{}{}, emptyWalker) 27 | assert.NoError(t, err) 28 | }) 29 | 30 | t.Run("init.env", func(t *testing.T) { 31 | _ = os.Setenv("TEST_V1", "test") 32 | _ = os.Setenv("TEST_V2", "1") 33 | 34 | var testStruct = struct { 35 | V1 string `env:"TEST_V1"` 36 | V2 int `env:"TEST_V2"` 37 | }{} 38 | 39 | err := StructWalk(ctx, &testStruct, func(ctx context.Context, curObj StructWalkObject, field StructWalkField, path []string) error { 40 | assert.True(t, field.IsEmpty()) 41 | err := field.SetValue(ctx, os.Getenv(field.Tag("env"))) 42 | if assert.NoError(t, err, `set value for field "%s"`, field.Name()) { 43 | if field.Name() == "V1" { 44 | assert.Equal(t, "TEST_V1", field.Tag("env")) 45 | assert.Equal(t, "test", field.Value()) 46 | } 47 | if field.Name() == "V2" { 48 | assert.Equal(t, "TEST_V2", field.Tag("env")) 49 | assert.Equal(t, 1, field.Value()) 50 | } 51 | } 52 | return err 53 | }) 54 | assert.NoError(t, err) 55 | }) 56 | 57 | t.Run("init.nested", func(t *testing.T) { 58 | type ( 59 | N2 struct { 60 | Text string `field:"text"` 61 | } 62 | N1 struct { 63 | V1 string `field:"v1"` 64 | V2 int `field:"v2"` 65 | N2 N2 `field:"n2"` 66 | } 67 | nestedStruct struct { 68 | T time.Time `field:"t"` 69 | V1 string `field:"v1"` 70 | V2 int `field:"v2"` 71 | N1 N1 `field:"n1"` 72 | } 73 | ) 74 | source := map[string]any{ 75 | "t": "2021-01-01T00:00:00Z", 76 | "v1": "test", 77 | "v2": 1, 78 | "n1": map[string]any{ 79 | "v1": "test", 80 | "v2": "1", 81 | "n2": map[string]any{ 82 | "text": "test", 83 | }, 84 | }, 85 | } 86 | testStruct := nestedStruct{} 87 | targetStruct := nestedStruct{ 88 | T: time.Date(2021, 1, 1, 0, 0, 0, 0, time.UTC), 89 | V1: "test", 90 | V2: 1, 91 | N1: N1{ 92 | V1: "test", 93 | V2: 1, 94 | N2: N2{Text: "test"}, 95 | }, 96 | } 97 | 98 | err := StructWalk(ctx, &testStruct, func(ctx context.Context, curObj StructWalkObject, field StructWalkField, path []string) error { 99 | if field.RefValue().Kind() == reflect.Struct { 100 | switch field.Value().(type) { 101 | case time.Time: 102 | default: 103 | return nil 104 | } 105 | } 106 | data := source 107 | for _, p := range path { 108 | data = data[p].(map[string]any) 109 | } 110 | 111 | if field.RefValue().Kind() != reflect.Struct { 112 | assert.True(t, field.IsEmpty()) 113 | } 114 | err := field.SetValue(ctx, data[field.Tag("field")]) 115 | assert.NoError(t, err, `set value for field "%s.%s"`, strings.Join(path, "."), field.Name()) 116 | return err 117 | }, WalkWithPathTag("field")) 118 | 119 | assert.NoError(t, err) 120 | assert.True(t, reflect.DeepEqual(testStruct, targetStruct), "compare struct: %#v", testStruct) 121 | }) 122 | } 123 | -------------------------------------------------------------------------------- /number.go: -------------------------------------------------------------------------------- 1 | package gocast 2 | 3 | import ( 4 | "strconv" 5 | "strings" 6 | 7 | "golang.org/x/exp/constraints" 8 | ) 9 | 10 | // Numeric data type 11 | type Numeric interface { 12 | constraints.Integer | constraints.Float 13 | } 14 | 15 | // TryNumber converts from types which could be numbers 16 | func TryNumber[R Numeric](v any) (R, error) { 17 | switch v := v.(type) { 18 | case nil: 19 | return R(0), nil 20 | case R: 21 | return v, nil 22 | case *R: 23 | return *v, nil 24 | } 25 | switch v := v.(type) { 26 | case string: 27 | if strings.Contains(v, ".") || strings.Contains(v, "e") || strings.Contains(v, "E") { 28 | rval, err := strconv.ParseFloat(v, 64) 29 | return R(rval), err 30 | } 31 | rval, err := strconv.ParseInt(v, 10, 64) 32 | return R(rval), err 33 | case []byte: 34 | s := string(v) 35 | if strings.Contains(s, ".") || strings.Contains(s, "e") || strings.Contains(s, "E") { 36 | rval, err := strconv.ParseFloat(s, 64) 37 | return R(rval), err 38 | } 39 | rval, err := strconv.ParseInt(s, 10, 64) 40 | return R(rval), err 41 | case bool: 42 | if v { 43 | return 1, nil 44 | } 45 | return 0, nil 46 | case int: 47 | return R(v), nil 48 | case int8: 49 | return R(v), nil 50 | case int16: 51 | return R(v), nil 52 | case int32: 53 | return R(v), nil 54 | case int64: 55 | return R(v), nil 56 | case uint: 57 | return R(v), nil 58 | case uint8: 59 | return R(v), nil 60 | case uint16: 61 | return R(v), nil 62 | case uint32: 63 | return R(v), nil 64 | case uintptr: 65 | return R(v), nil 66 | case uint64: 67 | return R(v), nil 68 | case float32: 69 | return R(v), nil 70 | case float64: 71 | return R(v), nil 72 | } 73 | return R(0), ErrUnsupportedNumericType 74 | } 75 | 76 | // Number converts from types which could be numbers or returns 0 77 | func Number[R Numeric](v any) R { 78 | res, _ := TryNumber[R](v) 79 | return res 80 | } 81 | 82 | // IsNumeric returns true if input is a numeric 83 | func IsNumericStr(s string) bool { 84 | if len(s) > 0 && (s[0] == '-' || s[0] == '+') { 85 | s = s[1:] 86 | } 87 | if len(s) == 0 || s[0] == '-' || s[0] == '+' { 88 | return false 89 | } 90 | switch { 91 | case strings.HasPrefix(s, "0x") || strings.HasPrefix(s, "0X"): 92 | return IsStrContainsOf(s[2:], "0123456789abcdefABCDEF") 93 | case strings.HasPrefix(s, "0o") || strings.HasPrefix(s, "0O") || (s[0] == '0' && len(s) > 1 && s[1] >= '0' && s[1] <= '7'): 94 | return IsStrContainsOf(s[2:], "01234567") 95 | case strings.HasPrefix(s, "0b") || strings.HasPrefix(s, "0B"): 96 | return IsStrContainsOf(s[2:], "01") 97 | } 98 | return IsNumeric10Str(s) 99 | } 100 | 101 | // IsNumeric10Str returns true if input string is a numeric in base 10 102 | func IsNumeric10Str(s string) bool { 103 | dot := false 104 | esimbol := -1 105 | isStart := false 106 | for i, c := range s { 107 | if c < '0' || c > '9' { 108 | if c == '.' && !dot && esimbol == -1 { 109 | dot = true 110 | continue 111 | } 112 | if i == 0 && (c == '-' || c == '+') { 113 | continue 114 | } 115 | if (c == 'e' || c == 'E') && isStart && esimbol == -1 { 116 | esimbol = i 117 | continue 118 | } 119 | if esimbol != -1 && (c == '-' || c == '+') && esimbol == i-1 { 120 | continue 121 | } 122 | return false 123 | } else { 124 | isStart = true 125 | } 126 | } 127 | return isStart && esimbol != len(s)-1 128 | } 129 | 130 | // IsNumericOnlyStr returns true if input string is a numeric only 131 | func IsNumericOnlyStr(s string) bool { 132 | for _, c := range s { 133 | if c < '0' || c > '9' { 134 | return false 135 | } 136 | } 137 | return true 138 | } 139 | -------------------------------------------------------------------------------- /bool.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2014 Dmitry Ponomarev 2 | // 3 | // Permission is hereby granted, free of charge, to any person obtaining a copy of 4 | // this software and associated documentation files (the "Software"), to deal in 5 | // the Software without restriction, including without limitation the rights to 6 | // use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 7 | // the Software, and to permit persons to whom the Software is furnished to do so, 8 | // subject to the following conditions: 9 | // 10 | // The above copyright notice and this permission notice shall be included in all 11 | // copies or substantial portions of the Software. 12 | // 13 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 15 | // FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 16 | // COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 17 | // IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 18 | // CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 19 | 20 | package gocast 21 | 22 | import ( 23 | "bytes" 24 | "reflect" 25 | "strings" 26 | ) 27 | 28 | var bytesType = reflect.TypeOf([]byte(nil)) 29 | 30 | // ReflectToBool returns boolean from reflection 31 | func ReflectToBool(v reflect.Value) bool { 32 | if !v.IsValid() { 33 | return false 34 | } 35 | switch v.Kind() { 36 | case reflect.String: 37 | val := v.String() 38 | return val == "1" || val == "true" || val == "t" 39 | case reflect.Slice: 40 | if v.Type() == bytesType { 41 | bv := v.Interface().([]byte) 42 | return len(bv) != 0 && (false || 43 | bytes.Equal(bv, []byte("1")) || 44 | bytes.Equal(bv, []byte("true")) || 45 | bytes.Equal(bv, []byte("t"))) 46 | } 47 | return v.Len() != 0 48 | case reflect.Array, reflect.Map: 49 | return v.Len() != 0 50 | case reflect.Bool: 51 | return v.Bool() 52 | case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: 53 | return v.Int() != 0 54 | case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr: 55 | return v.Uint() != 0 56 | case reflect.Float32, reflect.Float64: 57 | return v.Float() != 0 58 | } 59 | return false 60 | } 61 | 62 | // Bool from any other basic types 63 | func Bool(v any) bool { 64 | switch bv := v.(type) { 65 | case nil: 66 | return false 67 | case string: 68 | return bv == "1" || bv == "T" || bv == "t" || strings.EqualFold(bv, "true") 69 | case []byte: 70 | return len(bv) != 0 && (false || 71 | bytes.Equal(bv, []byte("1")) || 72 | bytes.Equal(bv, []byte("T")) || 73 | bytes.Equal(bv, []byte("t")) || 74 | bytes.EqualFold(bv, []byte("true"))) 75 | case bool: 76 | return bv 77 | case int: 78 | return bv != 0 79 | case int8: 80 | return bv != 0 81 | case int16: 82 | return bv != 0 83 | case int32: 84 | return bv != 0 85 | case int64: 86 | return bv != 0 87 | case uint: 88 | return bv != 0 89 | case uint8: 90 | return bv != 0 91 | case uint16: 92 | return bv != 0 93 | case uint32: 94 | return bv != 0 95 | case uint64: 96 | return bv != 0 97 | case uintptr: 98 | return bv != 0 99 | case float32: 100 | return bv != 0 101 | case float64: 102 | return bv != 0 103 | case []int: 104 | return len(bv) != 0 105 | case []int8: 106 | return len(bv) != 0 107 | case []int16: 108 | return len(bv) != 0 109 | case []int32: 110 | return len(bv) != 0 111 | case []uint64: 112 | return len(bv) != 0 113 | case []uint: 114 | return len(bv) != 0 115 | case []uint16: 116 | return len(bv) != 0 117 | case []uint32: 118 | return len(bv) != 0 119 | case []int64: 120 | return len(bv) != 0 121 | case []float32: 122 | return len(bv) != 0 123 | case []float64: 124 | return len(bv) != 0 125 | case []bool: 126 | return len(bv) != 0 127 | case []any: 128 | return len(bv) != 0 129 | } 130 | return ReflectToBool(reflect.ValueOf(v)) 131 | } 132 | -------------------------------------------------------------------------------- /map_test.go: -------------------------------------------------------------------------------- 1 | package gocast 2 | 3 | import ( 4 | "context" 5 | "reflect" 6 | "testing" 7 | 8 | "github.com/stretchr/testify/assert" 9 | ) 10 | 11 | func TestMap(t *testing.T) { 12 | assert.Equal(t, true, reflect.DeepEqual(map[string]string{"1": "2"}, 13 | Map[string, string](map[int]int{1: 2}))) 14 | assert.Equal(t, true, reflect.DeepEqual(map[string]string{"1": "2"}, 15 | Map[string, string](map[any]any{1: 2}))) 16 | assert.Equal(t, true, reflect.DeepEqual(map[float64]float32{1: 2}, 17 | Map[float64, float32](map[int16]string{1: "2.0"}))) 18 | assert.Equal(t, true, reflect.DeepEqual(map[string]string{"Foo": "boo"}, 19 | MapRecursive[string, string](struct{ Foo string }{Foo: "boo"}))) 20 | 21 | var target1 = map[any]any{} 22 | err := ToMap(&target1, struct{ Foo string }{Foo: "boo"}, true) 23 | assert.NoError(t, err) 24 | assert.Equal(t, true, reflect.DeepEqual(map[any]any{"Foo": "boo"}, target1)) 25 | err = ToMap(&target1, map[string]any{"Foo": "boo"}, true) 26 | assert.NoError(t, err) 27 | assert.Equal(t, true, reflect.DeepEqual(map[any]any{"Foo": "boo"}, target1)) 28 | 29 | var target2 = map[string]any{} 30 | err = ToMap(&target2, struct{ Foo string }{Foo: "boo"}, true) 31 | assert.NoError(t, err) 32 | assert.Equal(t, true, reflect.DeepEqual(map[string]any{"Foo": "boo"}, target2)) 33 | err = ToMap(&target2, map[string]any{"Foo": "boo"}, true) 34 | assert.NoError(t, err) 35 | assert.Equal(t, true, reflect.DeepEqual(map[string]any{"Foo": "boo"}, target2)) 36 | 37 | var target3 = map[string]string{} 38 | err = ToMap(&target3, struct{ Foo string }{Foo: "boo"}, true) 39 | assert.NoError(t, err) 40 | assert.Equal(t, true, reflect.DeepEqual(map[string]string{"Foo": "boo"}, target3)) 41 | err = ToMap(&target3, map[string]any{"Foo": "boo"}, true) 42 | assert.NoError(t, err) 43 | assert.Equal(t, true, reflect.DeepEqual(map[string]string{"Foo": "boo"}, target3)) 44 | 45 | var target4 = map[string]int{} 46 | err = ToMap(&target4, struct{ Foo int }{Foo: 1}, true) 47 | assert.NoError(t, err) 48 | assert.Equal(t, true, reflect.DeepEqual(map[string]int{"Foo": 1}, target4)) 49 | err = ToMap(&target4, map[string]any{"Foo": 1}, true) 50 | assert.NoError(t, err) 51 | assert.Equal(t, true, reflect.DeepEqual(map[string]int{"Foo": 1}, target4)) 52 | 53 | var target5 = map[string]float64{} 54 | err = TryMapCopy(target5, struct{ Foo float64 }{Foo: 1.0}, true) 55 | assert.NoError(t, err) 56 | assert.Equal(t, true, reflect.DeepEqual(map[string]float64{"Foo": 1.0}, target5)) 57 | err = TryMapCopy(target5, map[string]any{"Foo": 1.0}, true) 58 | assert.NoError(t, err) 59 | assert.Equal(t, true, reflect.DeepEqual(map[string]float64{"Foo": 1.0}, target5)) 60 | 61 | target6 := MapContext[string, float32](context.TODO(), struct{ Foo float32 }{Foo: 1.0}) 62 | assert.Equal(t, true, reflect.DeepEqual(map[string]float32{"Foo": 1.0}, target6)) 63 | 64 | target7 := MapRecursiveContext[string, map[string]any](context.TODO(), struct{ Foo struct{ Bar string } }{Foo: struct{ Bar string }{Bar: "boo"}}) 65 | assert.Equal(t, true, reflect.DeepEqual(map[string]map[string]any{"Foo": {"Bar": "boo"}}, target7)) 66 | 67 | // Nil check for map values 68 | var nilMap = map[string]any{"default": nil, "sub1": []any{nil}, "sub2": map[string]any{"n1": nil, "n2": nil, "n3": nil}} 69 | var target8 = map[string]any{} 70 | err = ToMap(&target8, nilMap, true) 71 | assert.NoError(t, err) 72 | assert.Equal(t, true, reflect.DeepEqual(nilMap, target8)) 73 | } 74 | 75 | func TestIsMap(t *testing.T) { 76 | tests := []struct { 77 | src any 78 | trg bool 79 | }{ 80 | {src: map[string]string{"1": "2"}, trg: true}, 81 | {src: map[string]int{"1": 2}, trg: true}, 82 | {src: map[string]float64{"1": 2.0}, trg: true}, 83 | {src: map[string]float32{"1": 2.0}, trg: true}, 84 | {src: map[any]uint{"1": 2}, trg: true}, 85 | {src: map[any]uint8{"1": 2}, trg: true}, 86 | {src: map[any]uint16{"1": 2}, trg: true}, 87 | {src: 1, trg: false}, 88 | {src: "1", trg: false}, 89 | {src: []string{"1"}, trg: false}, 90 | {src: []int{1}, trg: false}, 91 | {src: []float64{1.0}, trg: false}, 92 | {src: []float32{1.0}, trg: false}, 93 | {src: []uint{1}, trg: false}, 94 | {src: []uint8{1}, trg: false}, 95 | } 96 | 97 | for _, test := range tests { 98 | assert.Equal(t, test.trg, IsMap(test.src)) 99 | } 100 | } 101 | -------------------------------------------------------------------------------- /string.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2014 Dmitry Ponomarev 2 | // 3 | // Permission is hereby granted, free of charge, to any person obtaining a copy of 4 | // this software and associated documentation files (the "Software"), to deal in 5 | // the Software without restriction, including without limitation the rights to 6 | // use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 7 | // the Software, and to permit persons to whom the Software is furnished to do so, 8 | // subject to the following conditions: 9 | // 10 | // The above copyright notice and this permission notice shall be included in all 11 | // copies or substantial portions of the Software. 12 | // 13 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 15 | // FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 16 | // COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 17 | // IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 18 | // CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 19 | 20 | package gocast 21 | 22 | import ( 23 | "fmt" 24 | "reflect" 25 | "strconv" 26 | "strings" 27 | ) 28 | 29 | // TryReflectStr converts reflection value to string 30 | func TryReflectStr(v reflect.Value) (string, error) { 31 | if !v.IsValid() { 32 | return ``, nil 33 | } 34 | switch v.Kind() { 35 | case reflect.String: 36 | return v.String(), nil 37 | case reflect.Slice, reflect.Array: 38 | if v.Type().Elem().Kind() == reflect.Uint8 { 39 | return string(v.Bytes()), nil 40 | } 41 | case reflect.Bool: 42 | if v.Bool() { 43 | return "true", nil 44 | } 45 | return "false", nil 46 | case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: 47 | return strconv.FormatInt(v.Int(), 10), nil 48 | case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr: 49 | return strconv.FormatUint(v.Uint(), 10), nil 50 | case reflect.Float32, reflect.Float64: 51 | return strconv.FormatFloat(v.Float(), 'G', -1, 64), nil 52 | } 53 | return fmt.Sprintf("%v", v.Interface()), nil 54 | } 55 | 56 | // ReflectStr converts reflection value to string 57 | // 58 | //go:inline 59 | func ReflectStr(v reflect.Value) string { 60 | s, _ := TryReflectStr(v) 61 | return s 62 | } 63 | 64 | // TryStr from any type 65 | func TryStr(v any) (string, error) { 66 | switch val := v.(type) { 67 | case nil: 68 | return ``, nil 69 | case string: 70 | return val, nil 71 | case []byte: 72 | return string(val), nil 73 | case bool: 74 | if val { 75 | return "true", nil 76 | } 77 | return "false", nil 78 | case int: 79 | return strconv.FormatInt(int64(val), 10), nil 80 | case int8: 81 | return strconv.FormatInt(int64(val), 10), nil 82 | case int16: 83 | return strconv.FormatInt(int64(val), 10), nil 84 | case int32: 85 | return strconv.FormatInt(int64(val), 10), nil 86 | case int64: 87 | return strconv.FormatInt(val, 10), nil 88 | case uint: 89 | return strconv.FormatUint(uint64(val), 10), nil 90 | case uint8: 91 | return strconv.FormatUint(uint64(val), 10), nil 92 | case uint16: 93 | return strconv.FormatUint(uint64(val), 10), nil 94 | case uint32: 95 | return strconv.FormatUint(uint64(val), 10), nil 96 | case uint64: 97 | return strconv.FormatUint(val, 10), nil 98 | case float32: 99 | return strconv.FormatFloat(float64(val), 'G', -1, 64), nil 100 | case float64: 101 | return strconv.FormatFloat(val, 'G', -1, 64), nil 102 | case reflect.Value: 103 | return TryReflectStr(reflectTarget(val)) 104 | } 105 | val := reflectTarget(reflect.ValueOf(v)) 106 | return fmt.Sprintf("%v", val.Interface()), nil 107 | } 108 | 109 | // Str returns string value from any type 110 | // 111 | //go:inline 112 | func Str(v any) string { 113 | s, _ := TryStr(v) 114 | return s 115 | } 116 | 117 | // IsStr returns true if value is string 118 | func IsStr(v any) bool { 119 | if v == nil { 120 | return false 121 | } 122 | _, ok := v.(string) 123 | return ok 124 | } 125 | 126 | // IsStrContainsOf returns true if input string contains only chars from subset 127 | func IsStrContainsOf(s, subset string) bool { 128 | if len(s) == 0 { 129 | return false 130 | } 131 | for _, c := range s { 132 | if !strings.ContainsRune(subset, c) { 133 | return false 134 | } 135 | } 136 | return true 137 | } 138 | -------------------------------------------------------------------------------- /examples/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "log" 6 | 7 | "github.com/demdxx/gocast/v2" 8 | ) 9 | 10 | func main() { 11 | // Basic type casting examples 12 | fmt.Println("=== Basic Type Casting ===") 13 | fmt.Printf("String to int: %d\n", gocast.Number[int]("42")) 14 | fmt.Printf("Int to string: %s\n", gocast.Str(123)) 15 | fmt.Printf("Float to int: %d\n", gocast.Number[int](3.14)) 16 | 17 | // Deep copy examples 18 | fmt.Println("\n=== Deep Copy Examples ===") 19 | 20 | // 1. Simple struct copy 21 | type Person struct { 22 | Name string 23 | Age int 24 | } 25 | 26 | original := Person{Name: "Alice", Age: 30} 27 | copied, err := gocast.TryCopy(original) 28 | if err != nil { 29 | log.Fatal(err) 30 | } 31 | fmt.Printf("Original: %+v\n", original) 32 | fmt.Printf("Copied: %+v\n", copied) 33 | 34 | // 2. Slice copy with specialized function 35 | originalSlice := []int{1, 2, 3, 4, 5} 36 | copiedSlice := gocast.CopySlice(originalSlice) 37 | 38 | // Modify original to show they're independent 39 | originalSlice[0] = 999 40 | fmt.Printf("Original slice: %v\n", originalSlice) 41 | fmt.Printf("Copied slice: %v\n", copiedSlice) 42 | 43 | // 3. Map copy with specialized function 44 | originalMap := map[string]int{"a": 1, "b": 2, "c": 3} 45 | copiedMap := gocast.CopyMap(originalMap) 46 | 47 | originalMap["a"] = 999 48 | fmt.Printf("Original map: %v\n", originalMap) 49 | fmt.Printf("Copied map: %v\n", copiedMap) 50 | 51 | // 4. Complex nested structure 52 | type Address struct { 53 | City string 54 | Country string 55 | } 56 | 57 | type User struct { 58 | Name string 59 | Age int 60 | Address Address 61 | Tags []string 62 | Metadata map[string]any 63 | } 64 | 65 | complexUser := User{ 66 | Name: "Bob", 67 | Age: 25, 68 | Address: Address{ 69 | City: "New York", 70 | Country: "USA", 71 | }, 72 | Tags: []string{"admin", "user"}, 73 | Metadata: map[string]any{"role": "admin", "active": true}, 74 | } 75 | 76 | copiedUser, err := gocast.TryCopy(complexUser) 77 | if err != nil { 78 | log.Fatal(err) 79 | } 80 | 81 | // Modify original to demonstrate deep copy 82 | complexUser.Address.City = "Los Angeles" 83 | complexUser.Tags[0] = "superadmin" 84 | complexUser.Metadata["role"] = "superadmin" 85 | 86 | fmt.Printf("Original user: %+v\n", complexUser) 87 | fmt.Printf("Copied user: %+v\n", copiedUser) 88 | 89 | // 5. Copy with options 90 | fmt.Println("\n=== Copy with Options ===") 91 | 92 | type Config struct { 93 | PublicSetting string 94 | privateSetting string // unexported 95 | NestedConfig *Config 96 | } 97 | 98 | config := &Config{ 99 | PublicSetting: "public", 100 | privateSetting: "private", 101 | NestedConfig: &Config{ 102 | PublicSetting: "nested_public", 103 | privateSetting: "nested_private", 104 | }, 105 | } 106 | 107 | // Copy with options: ignore unexported fields, limit depth 108 | opts := gocast.CopyOptions{ 109 | IgnoreUnexportedFields: true, 110 | MaxDepth: 1, 111 | } 112 | 113 | limitedCopy, err := gocast.TryCopyWithOptions(config, opts) 114 | if err != nil { 115 | log.Fatal(err) 116 | } 117 | 118 | fmt.Printf("Original config: %+v\n", config) 119 | fmt.Printf("Limited copy: %+v\n", limitedCopy) 120 | fmt.Printf("Nested config copied: %v\n", limitedCopy.NestedConfig != nil) 121 | 122 | // 6. Circular reference handling 123 | fmt.Println("\n=== Circular Reference Handling ===") 124 | 125 | type Node struct { 126 | Value int 127 | Next *Node 128 | } 129 | 130 | node1 := &Node{Value: 1} 131 | node2 := &Node{Value: 2} 132 | node1.Next = node2 133 | node2.Next = node1 // Create circular reference 134 | 135 | copiedNode, err := gocast.TryCopy(node1) 136 | if err != nil { 137 | log.Fatal(err) 138 | } 139 | 140 | fmt.Printf("Original node1 value: %d\n", node1.Value) 141 | fmt.Printf("Copied node1 value: %d\n", copiedNode.Value) 142 | fmt.Printf("Circular reference preserved: %v\n", copiedNode.Next.Next == copiedNode) 143 | 144 | // 7. Performance comparison 145 | fmt.Println("\n=== Performance Examples ===") 146 | 147 | slice := make([]int, 1000) 148 | for i := range slice { 149 | slice[i] = i 150 | } 151 | 152 | // Using general TryCopy 153 | _, err = gocast.TryCopy(slice) 154 | if err != nil { 155 | log.Fatal(err) 156 | } 157 | fmt.Println("TryCopy: ✓") 158 | 159 | // Using specialized CopySlice (faster) 160 | _ = gocast.CopySlice(slice) 161 | fmt.Println("CopySlice: ✓ (faster)") 162 | 163 | // Using MustCopy (panics on error) 164 | _ = gocast.MustCopy(42) 165 | fmt.Println("MustCopy: ✓") 166 | 167 | fmt.Println("\n=== Examples completed successfully! ===") 168 | } 169 | -------------------------------------------------------------------------------- /int.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2014 Dmitry Ponomarev 2 | // 3 | // Permission is hereby granted, free of charge, to any person obtaining a copy of 4 | // this software and associated documentation files (the "Software"), to deal in 5 | // the Software without restriction, including without limitation the rights to 6 | // use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 7 | // the Software, and to permit persons to whom the Software is furnished to do so, 8 | // subject to the following conditions: 9 | // 10 | // The above copyright notice and this permission notice shall be included in all 11 | // copies or substantial portions of the Software. 12 | // 13 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 15 | // FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 16 | // COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 17 | // IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 18 | // CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 19 | 20 | package gocast 21 | 22 | import ( 23 | "reflect" 24 | "strconv" 25 | "strings" 26 | ) 27 | 28 | // ReflectToInt64 returns int64 from reflection 29 | func ReflectToInt64(v reflect.Value) int64 { 30 | switch v.Kind() { 31 | case reflect.String: 32 | var val int64 33 | var strVal = v.String() 34 | if strings.Contains(strVal, ".") || strings.Contains(strVal, "e") || strings.Contains(strVal, "E") { 35 | fval, _ := strconv.ParseFloat(strVal, 64) 36 | val = int64(fval) 37 | } else { 38 | val, _ = strconv.ParseInt(strVal, 10, 64) 39 | } 40 | return val 41 | case reflect.Bool: 42 | if v.Bool() { 43 | return 1 44 | } 45 | return 0 46 | case reflect.Slice, reflect.Array: 47 | switch v.Type().Elem().Kind() { 48 | case reflect.Uint8: 49 | var val int64 50 | str := string(v.Interface().([]byte)) 51 | if strings.Contains(str, ".") || strings.Contains(str, "e") || strings.Contains(str, "E") { 52 | fval, _ := strconv.ParseFloat(str, 64) 53 | val = int64(fval) 54 | } else { 55 | val, _ = strconv.ParseInt(str, 10, 64) 56 | } 57 | return val 58 | default: 59 | } 60 | case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: 61 | return v.Int() 62 | case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr: 63 | return int64(v.Uint()) 64 | case reflect.Float32, reflect.Float64: 65 | return int64(v.Float()) 66 | } 67 | return 0 68 | } 69 | 70 | // ToUint64ByReflect returns uint64 from reflection 71 | func ToUint64ByReflect(v reflect.Value) uint64 { 72 | switch v.Kind() { 73 | case reflect.String: 74 | var val uint64 75 | if strings.Contains(v.Interface().(string), ".") { 76 | fval, _ := strconv.ParseFloat(v.Interface().(string), 64) 77 | val = uint64(fval) 78 | } else { 79 | val, _ = strconv.ParseUint(v.Interface().(string), 10, 64) 80 | } 81 | return val 82 | case reflect.Bool: 83 | if v.Bool() { 84 | return 1 85 | } 86 | return 0 87 | case reflect.Slice, reflect.Array: 88 | switch v.Type().Elem().Kind() { 89 | case reflect.Uint8: 90 | var val uint64 91 | str := string(v.Interface().([]byte)) 92 | if strings.Contains(str, ".") || strings.Contains(str, "e") || strings.Contains(str, "E") { 93 | fval, _ := strconv.ParseFloat(str, 64) 94 | val = uint64(fval) 95 | } else { 96 | val, _ = strconv.ParseUint(str, 10, 64) 97 | } 98 | return val 99 | default: 100 | } 101 | case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: 102 | return uint64(v.Int()) 103 | case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr: 104 | return uint64(v.Uint()) 105 | case reflect.Float32, reflect.Float64: 106 | return uint64(v.Float()) 107 | } 108 | return 0 109 | } 110 | 111 | // Int from any other basic type 112 | // 113 | //go:inline 114 | func Int(v any) int { return Number[int](v) } 115 | 116 | // Int8 from any other basic type 117 | // 118 | //go:inline 119 | func Int8(v any) int8 { return Number[int8](v) } 120 | 121 | // Int16 from any other basic type 122 | // 123 | //go:inline 124 | func Int16(v any) int16 { return Number[int16](v) } 125 | 126 | // Int32 from any other basic type 127 | // 128 | //go:inline 129 | func Int32(v any) int32 { return Number[int32](v) } 130 | 131 | // Int64 from any other basic type 132 | // 133 | //go:inline 134 | func Int64(v any) int64 { return Number[int64](v) } 135 | 136 | // Uint from any other basic type 137 | // 138 | //go:inline 139 | func Uint(v any) uint { return Number[uint](v) } 140 | 141 | // Uint8 from any other basic type 142 | // 143 | //go:inline 144 | func Uint8(v any) uint8 { return Number[uint8](v) } 145 | 146 | // Uint16 from any other basic type 147 | // 148 | //go:inline 149 | func Uint16(v any) uint16 { return Number[uint16](v) } 150 | 151 | // Uint32 from any other basic type 152 | // 153 | //go:inline 154 | func Uint32(v any) uint32 { return Number[uint32](v) } 155 | 156 | // Uint64 from any other basic type 157 | // 158 | //go:inline 159 | func Uint64(v any) uint64 { return Number[uint64](v) } 160 | -------------------------------------------------------------------------------- /slice_test.go: -------------------------------------------------------------------------------- 1 | package gocast 2 | 3 | import ( 4 | "context" 5 | "testing" 6 | 7 | "github.com/stretchr/testify/assert" 8 | ) 9 | 10 | type testSliceStruct struct { 11 | ID int 12 | Text string 13 | } 14 | 15 | type testSliceCastStruct struct { 16 | Text string 17 | } 18 | 19 | func (it *testSliceCastStruct) CastSet(ctx context.Context, v any) error { 20 | it.Text = Str(v) 21 | return nil 22 | } 23 | 24 | func TestToSlice(t *testing.T) { 25 | type customVal string 26 | tests := []struct { 27 | src any 28 | trg any 29 | err error 30 | cfn func(v any) (any, error) 31 | }{ 32 | { 33 | src: []int{1, 2, 3, 4}, 34 | trg: []string{"1", "2", "3", "4"}, 35 | cfn: func(v any) (any, error) { return AnySlice[string](v), nil }, 36 | }, 37 | { 38 | src: []int8{1, 2, 3, 4}, 39 | trg: []string{"1", "2", "3", "4"}, 40 | cfn: func(v any) (any, error) { return AnySlice[string](v), nil }, 41 | }, 42 | { 43 | src: []int16{1, 2, 3, 4}, 44 | trg: []string{"1", "2", "3", "4"}, 45 | cfn: func(v any) (any, error) { return AnySlice[string](v), nil }, 46 | }, 47 | { 48 | src: []int32{1, 2, 3, 4}, 49 | trg: []string{"1", "2", "3", "4"}, 50 | cfn: func(v any) (any, error) { return AnySlice[string](v), nil }, 51 | }, 52 | { 53 | src: []int64{1, 2, 3, 4}, 54 | trg: []string{"1", "2", "3", "4"}, 55 | cfn: func(v any) (any, error) { return AnySlice[string](v), nil }, 56 | }, 57 | { 58 | src: []uint{1, 2, 3, 4}, 59 | trg: []string{"1", "2", "3", "4"}, 60 | cfn: func(v any) (any, error) { return AnySlice[string](v), nil }, 61 | }, 62 | { 63 | src: []uint8{1, 2, 3, 4}, 64 | trg: []string{"1", "2", "3", "4"}, 65 | cfn: func(v any) (any, error) { return AnySlice[string](v), nil }, 66 | }, 67 | { 68 | src: []uint16{1, 2, 3, 4}, 69 | trg: []string{"1", "2", "3", "4"}, 70 | cfn: func(v any) (any, error) { return AnySlice[string](v), nil }, 71 | }, 72 | { 73 | src: []uint32{1, 2, 3, 4}, 74 | trg: []string{"1", "2", "3", "4"}, 75 | cfn: func(v any) (any, error) { return AnySlice[string](v), nil }, 76 | }, 77 | { 78 | src: []uint64{1, 2, 3, 4}, 79 | trg: []string{"1", "2", "3", "4"}, 80 | cfn: func(v any) (any, error) { return AnySlice[string](v), nil }, 81 | }, 82 | { 83 | src: []customVal{"1", "2", "3", "4"}, 84 | trg: []string{"1", "2", "3", "4"}, 85 | cfn: func(v any) (any, error) { return AnySlice[string](v), nil }, 86 | }, 87 | { 88 | src: []any{"1", "2.5", 6, 1.2}, 89 | trg: []int{1, 2, 6, 1}, 90 | cfn: func(v any) (any, error) { return AnySlice[int](v), nil }, 91 | }, 92 | { 93 | src: []float64{1, 2.5, 6, 1.2}, 94 | trg: []int{1, 2, 6, 1}, 95 | cfn: func(v any) (any, error) { return AnySlice[int](v), nil }, 96 | }, 97 | { 98 | src: []float32{1, 2.5, 6, 1.2}, 99 | trg: []int{1, 2, 6, 1}, 100 | cfn: func(v any) (any, error) { return AnySlice[int](v), nil }, 101 | }, 102 | { 103 | src: []bool{true, false}, 104 | trg: []int{1, 0}, 105 | cfn: func(v any) (any, error) { return AnySlice[int](v), nil }, 106 | }, 107 | { 108 | src: []any{"1", "2.5", 6, 1.2}, 109 | trg: []any{"1", "2.5", 6, 1.2}, 110 | cfn: func(v any) (any, error) { return AnySlice[any](v), nil }, 111 | }, 112 | { 113 | src: []int{1, 2, 3, 4}, 114 | trg: []any{1, 2, 3, 4}, 115 | cfn: func(v any) (any, error) { return AnySlice[any](v), nil }, 116 | }, 117 | { 118 | src: []int64{1, 2, 3, 4}, 119 | trg: []any{int64(1), int64(2), int64(3), int64(4)}, 120 | cfn: func(v any) (any, error) { return AnySlice[any](v), nil }, 121 | }, 122 | { 123 | src: []any{"1", "2.5", 6, 1.2, "999.5"}, 124 | trg: []float64{1, 2.5, 6, 1.2, 999.5}, 125 | cfn: func(v any) (any, error) { return AnySlice[float64](v), nil }, 126 | }, 127 | { 128 | src: []string{"1", "2.5", "6", "1.2", "999.5"}, 129 | trg: []float64{1, 2.5, 6, 1.2, 999.5}, 130 | cfn: func(v any) (any, error) { return AnySlice[float64](v), nil }, 131 | }, 132 | { 133 | src: []any{"1", "2.5", 6, 1.2, "999.5", true}, 134 | trg: []int{1, 2, 6, 1, 999, 1}, 135 | cfn: func(v any) (any, error) { arr := []int{}; err := TryToAnySlice(&arr, v); return arr, err }, 136 | }, 137 | { 138 | src: []any{"1", "2.5", 6, 1.2, "999.5", true}, 139 | trg: []int{1, 2, 6, 1, 999, 1}, 140 | cfn: func(v any) (any, error) { return AnySlice[int](v), nil }, 141 | }, 142 | { 143 | src: []any{"1", "2.5", 6, 1.2, "999.5", true}, 144 | trg: []int{1, 2, 6, 1, 999, 1}, 145 | cfn: func(v any) (any, error) { return Slice[int](v.([]any)), nil }, 146 | }, 147 | { 148 | src: []map[any]any{{"ID": 1, "Text": "text1"}, {"ID": 2, "Text": "text1"}}, 149 | trg: []testSliceStruct{{ID: 1, Text: "text1"}, {ID: 2, Text: "text1"}}, 150 | cfn: func(v any) (any, error) { return Slice[testSliceStruct](v.([]map[any]any)), nil }, 151 | }, 152 | { 153 | src: []any{"text1", "text2"}, 154 | trg: []testSliceCastStruct{{Text: "text1"}, {Text: "text2"}}, 155 | cfn: func(v any) (any, error) { return Slice[testSliceCastStruct](v.([]any)), nil }, 156 | }, 157 | { 158 | src: []any{"text1", "text2"}, 159 | trg: []any{"text1", "text2"}, 160 | cfn: func(v any) (any, error) { return Slice[any](v.([]any)), nil }, 161 | }, 162 | { 163 | src: nil, 164 | err: ErrInvalidParams, 165 | cfn: func(v any) (any, error) { return TryAnySlice[int](v) }, 166 | }, 167 | { 168 | src: 1, 169 | err: ErrInvalidParams, 170 | cfn: func(v any) (any, error) { return TryAnySlice[int](v) }, 171 | }, 172 | } 173 | for _, test := range tests { 174 | res, err := test.cfn(test.src) 175 | if test.err != nil { 176 | assert.ErrorContains(t, err, test.err.Error()) 177 | } else if assert.NoError(t, err) { 178 | assert.ElementsMatch(t, test.trg, res) 179 | } 180 | } 181 | } 182 | 183 | func TestIsSlice(t *testing.T) { 184 | assert.True(t, IsSlice([]int{})) 185 | assert.True(t, IsSlice([]bool{})) 186 | assert.True(t, IsSlice([]testSliceCastStruct{})) 187 | assert.True(t, IsSlice(([]any)(nil))) 188 | assert.False(t, IsSlice("not a slice")) 189 | } 190 | -------------------------------------------------------------------------------- /int_test.go: -------------------------------------------------------------------------------- 1 | package gocast 2 | 3 | import ( 4 | "reflect" 5 | "testing" 6 | ) 7 | 8 | func TestToInt(t *testing.T) { 9 | var tests = []struct { 10 | src any 11 | target int 12 | }{ 13 | {src: 120, target: 120}, 14 | {src: int8(121), target: 121}, 15 | {src: int16(121), target: 121}, 16 | {src: int32(121), target: 121}, 17 | {src: int64(121), target: 121}, 18 | {src: uint(121), target: 121}, 19 | {src: uint8(121), target: 121}, 20 | {src: uint16(121), target: 121}, 21 | {src: uint32(121), target: 121}, 22 | {src: uint64(121), target: 121}, 23 | {src: uintptr(121), target: 121}, 24 | {src: float64(122.), target: 122}, 25 | {src: float32(122.), target: 122}, 26 | {src: "123", target: 123}, 27 | {src: "120.0", target: 120}, 28 | {src: "-120.", target: -120}, 29 | {src: []byte("125."), target: 125}, 30 | {src: []byte("125"), target: 125}, 31 | {src: true, target: 1}, 32 | {src: false, target: 0}, 33 | } 34 | 35 | for _, test := range tests { 36 | if Int(test.src) != test.target { 37 | t.Errorf("target must be equal %v != %d", test.target, test.target) 38 | } 39 | if Int(test.src) != test.target { 40 | t.Errorf("target must be equal %v != %d", test.target, test.target) 41 | } 42 | if Int8(test.src) != int8(test.target) { 43 | t.Errorf("target must be equal %v != %d", test.target, test.target) 44 | } 45 | if Int16(test.src) != int16(test.target) { 46 | t.Errorf("target must be equal %v != %d", test.target, test.target) 47 | } 48 | if Int32(test.src) != int32(test.target) { 49 | t.Errorf("target must be equal %v != %d", test.target, test.target) 50 | } 51 | if Int64(test.src) != int64(test.target) { 52 | t.Errorf("target must be equal %v != %d", test.target, test.target) 53 | } 54 | if test.target >= 0 { 55 | if Uint(test.src) != uint(test.target) { 56 | t.Errorf("target must be equal %v != %d", test.target, test.target) 57 | } 58 | if Uint8(test.src) != uint8(test.target) { 59 | t.Errorf("target must be equal %v != %d", test.target, test.target) 60 | } 61 | if Uint16(test.src) != uint16(test.target) { 62 | t.Errorf("target must be equal %v != %d", test.target, test.target) 63 | } 64 | if Uint32(test.src) != uint32(test.target) { 65 | t.Errorf("target must be equal %v != %d", test.target, test.target) 66 | } 67 | if Uint64(test.src) != uint64(test.target) { 68 | t.Errorf("target must be equal %v != %d", test.target, test.target) 69 | } 70 | } 71 | } 72 | } 73 | 74 | func TestToInt64ByReflect(t *testing.T) { 75 | var tests = []struct { 76 | src any 77 | target int 78 | }{ 79 | {src: 120, target: 120}, 80 | {src: int8(121), target: 121}, 81 | {src: int16(121), target: 121}, 82 | {src: int32(121), target: 121}, 83 | {src: int64(121), target: 121}, 84 | {src: uint(121), target: 121}, 85 | {src: uint8(121), target: 121}, 86 | {src: uint16(121), target: 121}, 87 | {src: uint32(121), target: 121}, 88 | {src: uint64(121), target: 121}, 89 | {src: uintptr(121), target: 121}, 90 | {src: float64(122.), target: 122}, 91 | {src: float32(122.), target: 122}, 92 | {src: "123", target: 123}, 93 | {src: "120.0", target: 120}, 94 | {src: "-120.", target: -120}, 95 | {src: []byte("125."), target: 125}, 96 | {src: []byte("125"), target: 125}, 97 | {src: true, target: 1}, 98 | {src: false, target: 0}, 99 | } 100 | 101 | for _, test := range tests { 102 | if int(ReflectToInt64(reflect.ValueOf(test.src))) != test.target { 103 | t.Errorf("target must be equal %v != %d", test.target, test.target) 104 | } 105 | } 106 | } 107 | 108 | func BenchmarkToInt(b *testing.B) { 109 | values := []any{120, uint64(122), "123", "120.0", "-120.", []byte("125."), true, false} 110 | for n := 0; n < b.N; n++ { 111 | _ = Int(values[n%len(values)]) 112 | } 113 | } 114 | 115 | func TestToUInt(t *testing.T) { 116 | var tests = []struct { 117 | src any 118 | target uint 119 | }{ 120 | {src: 120, target: 120}, 121 | {src: int8(121), target: 121}, 122 | {src: int16(121), target: 121}, 123 | {src: int32(121), target: 121}, 124 | {src: int64(121), target: 121}, 125 | {src: uint(121), target: 121}, 126 | {src: uint8(121), target: 121}, 127 | {src: uint16(121), target: 121}, 128 | {src: uint32(121), target: 121}, 129 | {src: uint64(121), target: 121}, 130 | {src: uintptr(121), target: 121}, 131 | {src: float64(122.), target: 122}, 132 | {src: float32(122.), target: 122}, 133 | {src: "123", target: 123}, 134 | {src: "120.0", target: 120}, 135 | {src: "120.", target: 120}, 136 | {src: []byte("125."), target: 125}, 137 | {src: []byte("125"), target: 125}, 138 | {src: true, target: 1}, 139 | {src: false, target: 0}, 140 | } 141 | 142 | for _, test := range tests { 143 | if Uint(test.src) != test.target { 144 | t.Errorf("target must be equal %v != %d", test.target, test.target) 145 | } 146 | } 147 | } 148 | 149 | func TestToUint64ByReflect(t *testing.T) { 150 | var tests = []struct { 151 | src any 152 | target uint 153 | }{ 154 | {src: 120, target: 120}, 155 | {src: int8(121), target: 121}, 156 | {src: int16(121), target: 121}, 157 | {src: int32(121), target: 121}, 158 | {src: int64(121), target: 121}, 159 | {src: uint(121), target: 121}, 160 | {src: uint8(121), target: 121}, 161 | {src: uint16(121), target: 121}, 162 | {src: uint32(121), target: 121}, 163 | {src: uint64(121), target: 121}, 164 | {src: uintptr(121), target: 121}, 165 | {src: float64(122.), target: 122}, 166 | {src: float32(122.), target: 122}, 167 | {src: "123", target: 123}, 168 | {src: "120.0", target: 120}, 169 | {src: "120.", target: 120}, 170 | {src: []byte("125."), target: 125}, 171 | {src: []byte("125"), target: 125}, 172 | {src: true, target: 1}, 173 | {src: false, target: 0}, 174 | } 175 | 176 | for _, test := range tests { 177 | if uint(ToUint64ByReflect(reflect.ValueOf(test.src))) != test.target { 178 | t.Errorf("target must be equal %v != %d", test.target, test.target) 179 | } 180 | } 181 | } 182 | 183 | func BenchmarkToUint(b *testing.B) { 184 | values := []any{120, int64(122), "123", "120.0", "120.", []byte("125."), true, false} 185 | for n := 0; n < b.N; n++ { 186 | _ = Uint(values[n%len(values)]) 187 | } 188 | } 189 | -------------------------------------------------------------------------------- /copy_test.go: -------------------------------------------------------------------------------- 1 | package gocast 2 | 3 | import ( 4 | "errors" 5 | "reflect" 6 | "testing" 7 | 8 | "github.com/stretchr/testify/assert" 9 | ) 10 | 11 | func testCopy[T any](t *testing.T, src T, expectErr error) T { 12 | dst, err := TryCopy(src) 13 | if expectErr != nil { 14 | assert.ErrorContains(t, err, expectErr.Error(), "error message mismatch for %T", src) 15 | } else if assert.NoError(t, err, "unexpected error for %T", src) { 16 | if IsNil(dst) && !IsNil(src) { 17 | t.Errorf("expected non-nil destination for %T, got nil", src) 18 | } 19 | if !assert.True(t, IsNil(dst) == IsNil(src), "expected destination nil state to match source for %T", src) { 20 | return dst 21 | } 22 | switch v := any(src).(type) { 23 | case nil: 24 | assert.Nil(t, dst, "expected nil destination for nil source") 25 | case []int, []string: 26 | assert.ElementsMatch(t, v, dst, "expected destination to match source for slice of int") 27 | case map[string]int: 28 | assert.True(t, reflect.DeepEqual(v, dst), "expected destination to match source for map of string to int") 29 | default: 30 | t1 := reflect.TypeOf(v) 31 | t2 := reflect.TypeOf(dst) 32 | if assert.True(t, t1 == t2, "expected destination type to match source type for %T", v) { 33 | if t1.Kind() == reflect.Struct { 34 | assert.True(t, reflect.DeepEqual(v, dst), "expected destination to match source for struct %T", v) 35 | } else { 36 | assert.Equal(t, v, dst, "expected destination type to match source type for %T", v) 37 | } 38 | } 39 | } 40 | } 41 | return dst 42 | } 43 | 44 | func TestCopy(t *testing.T) { 45 | type Inner struct { 46 | Value int 47 | S string 48 | Sl []int 49 | } 50 | type Outer struct { 51 | Inner Inner 52 | } 53 | t.Run("nil value", func(t *testing.T) { testCopy[any](t, nil, nil) }) 54 | t.Run("int value", func(t *testing.T) { testCopy(t, 42, nil) }) 55 | t.Run("string value", func(t *testing.T) { testCopy(t, "hello", nil) }) 56 | t.Run("slice of int", func(t *testing.T) { testCopy(t, []int{1, 2, 3}, nil) }) 57 | t.Run("map of string to int", func(t *testing.T) { testCopy(t, map[string]int{"a": 1, "b": 2}, nil) }) 58 | t.Run("struct value", func(t *testing.T) { testCopy(t, struct{ Name string }{Name: "test"}, nil) }) 59 | t.Run("unsupported type", func(t *testing.T) { 60 | testCopy(t, make(chan int), errors.New("unsupported type: chan int")) 61 | }) 62 | t.Run("deeply nested struct", func(t *testing.T) { 63 | src := Outer{Inner: Inner{ 64 | Value: 42, 65 | S: "nested", 66 | Sl: []int{1, 2, 3}, 67 | }} 68 | testCopy(t, src, nil) 69 | }) 70 | t.Run("deeply nested any struct", func(t *testing.T) { 71 | src := Outer{Inner: Inner{ 72 | Value: 42, 73 | S: "nested", 74 | Sl: []int{1, 2, 3}, 75 | }} 76 | assert.NotPanics(t, func() { 77 | dst := AnyCopy(src) 78 | assert.True(t, reflect.TypeOf(dst) == reflect.TypeOf(src), 79 | "expected destination type to match source type for deeply nested any struct") 80 | assert.True(t, reflect.DeepEqual(dst, src), 81 | "expected destination to match source for deeply nested any struct") 82 | }, "should not panic for deeply nested any struct") 83 | }) 84 | t.Run("nil pointer", func(t *testing.T) { 85 | var src *struct{ Name string } 86 | dst := testCopy(t, src, nil) 87 | assert.Nil(t, dst, "expected nil destination for nil pointer source") 88 | assert.True(t, reflect.TypeOf(dst) == reflect.TypeOf(src), 89 | "expected destination type to match source type for nil pointer") 90 | }) 91 | t.Run("non-nil pointer", func(t *testing.T) { 92 | src := &struct{ Name string }{Name: "pointer"} 93 | dst := testCopy(t, src, nil) 94 | dst.Name = "modified" 95 | assert.NotEqual(t, src.Name, dst.Name, "expected pointer copy to be independent") 96 | }) 97 | } 98 | 99 | func TestCopyCircularReferences(t *testing.T) { 100 | type Node struct { 101 | Value int 102 | Next *Node 103 | } 104 | 105 | // Create a circular reference 106 | node1 := &Node{Value: 1} 107 | node2 := &Node{Value: 2} 108 | node1.Next = node2 109 | node2.Next = node1 110 | 111 | // Test copying with circular references 112 | copied, err := TryCopy(node1) 113 | assert.NoError(t, err) 114 | assert.NotNil(t, copied) 115 | assert.Equal(t, node1.Value, copied.Value) 116 | assert.NotSame(t, node1, copied) // Should be different instances 117 | assert.Equal(t, copied.Next.Next, copied) // Should maintain circular reference 118 | } 119 | 120 | func BenchmarkCopy(b *testing.B) { 121 | type SimpleStruct struct { 122 | ID int 123 | Name string 124 | } 125 | 126 | type ComplexStruct struct { 127 | ID int 128 | Name string 129 | Values []int 130 | Nested SimpleStruct 131 | Mapping map[string]int 132 | } 133 | 134 | simpleData := SimpleStruct{ID: 1, Name: "test"} 135 | complexData := ComplexStruct{ 136 | ID: 1, 137 | Name: "complex", 138 | Values: []int{1, 2, 3, 4, 5}, 139 | Nested: SimpleStruct{ID: 2, Name: "nested"}, 140 | Mapping: map[string]int{"a": 1, "b": 2, "c": 3}, 141 | } 142 | 143 | b.Run("simple_int", func(b *testing.B) { 144 | for i := 0; i < b.N; i++ { 145 | _, _ = TryCopy(42) 146 | } 147 | }) 148 | 149 | b.Run("simple_string", func(b *testing.B) { 150 | for i := 0; i < b.N; i++ { 151 | _, _ = TryCopy("hello world") 152 | } 153 | }) 154 | 155 | b.Run("simple_struct", func(b *testing.B) { 156 | for i := 0; i < b.N; i++ { 157 | _, _ = TryCopy(simpleData) 158 | } 159 | }) 160 | 161 | b.Run("complex_struct", func(b *testing.B) { 162 | for i := 0; i < b.N; i++ { 163 | _, _ = TryCopy(complexData) 164 | } 165 | }) 166 | 167 | b.Run("slice", func(b *testing.B) { 168 | slice := []int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10} 169 | for i := 0; i < b.N; i++ { 170 | _, _ = TryCopy(slice) 171 | } 172 | }) 173 | 174 | b.Run("map", func(b *testing.B) { 175 | m := map[string]int{"a": 1, "b": 2, "c": 3, "d": 4, "e": 5} 176 | for i := 0; i < b.N; i++ { 177 | _, _ = TryCopy(m) 178 | } 179 | }) 180 | } 181 | 182 | func TestCopyWithOptions(t *testing.T) { 183 | // Test basic copying 184 | src := 42 185 | dst, err := TryCopyWithOptions(src, CopyOptions{}) 186 | assert.NoError(t, err) 187 | assert.Equal(t, src, dst) 188 | 189 | // Test with slice 190 | srcSlice := []int{1, 2, 3} 191 | dstSlice, err := TryCopyWithOptions(srcSlice, CopyOptions{}) 192 | assert.NoError(t, err) 193 | assert.ElementsMatch(t, srcSlice, dstSlice) 194 | } 195 | -------------------------------------------------------------------------------- /slice.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2014 Dmitry Ponomarev 2 | // 3 | // Permission is hereby granted, free of charge, to any person obtaining a copy of 4 | // this software and associated documentation files (the "Software"), to deal in 5 | // the Software without restriction, including without limitation the rights to 6 | // use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 7 | // the Software, and to permit persons to whom the Software is furnished to do so, 8 | // subject to the following conditions: 9 | // 10 | // The above copyright notice and this permission notice shall be included in all 11 | // copies or substantial portions of the Software. 12 | // 13 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 15 | // FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 16 | // COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 17 | // IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 18 | // CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 19 | 20 | package gocast 21 | 22 | import ( 23 | "context" 24 | "reflect" 25 | ) 26 | 27 | // TrySlice converts one type of array to other or resturns error 28 | // 29 | //go:inline 30 | func TrySlice[R any, S any](src []S, tags ...string) (res []R, err error) { 31 | return TrySliceContext[R](context.Background(), src, tags...) 32 | } 33 | 34 | // TrySliceContext converts one type of array to other or resturns error 35 | func TrySliceContext[R any, S any](ctx context.Context, src []S, tags ...string) (res []R, err error) { 36 | res = make([]R, len(src)) 37 | switch srcArr := any(src).(type) { 38 | case []R: 39 | copy(res, srcArr) 40 | default: 41 | for i, v := range src { 42 | var newVal R 43 | if newVal, err = TryCastContext[R](ctx, v); err != nil { 44 | return nil, err 45 | } else { 46 | res[i] = newVal 47 | } 48 | } 49 | } 50 | return res, nil 51 | } 52 | 53 | // Slice converts one type of array to other or resturns nil if not compatible 54 | // 55 | //go:inline 56 | func Slice[R any, S any](src []S, tags ...string) []R { 57 | return SliceContext[R](context.Background(), src, tags...) 58 | } 59 | 60 | // SliceContext converts one type of array to other or resturns nil if not compatible 61 | func SliceContext[R any, S any](ctx context.Context, src []S, tags ...string) []R { 62 | res, _ := TrySliceContext[R](ctx, src, tags...) 63 | return res 64 | } 65 | 66 | // TryAnySlice converts any input slice into destination type slice as return value 67 | func TryAnySlice[R any](src any, tags ...string) (res []R, err error) { 68 | return TryAnySliceContext[R](context.Background(), src, tags...) 69 | } 70 | 71 | // TryAnySliceContext converts any input slice into destination type slice as return value 72 | func TryAnySliceContext[R any](ctx context.Context, src any, tags ...string) ([]R, error) { 73 | res := []R{} 74 | err := TryToAnySliceContext(ctx, &res, src, tags...) 75 | return res, err 76 | } 77 | 78 | // AnySlice converts any input slice into destination type slice as return value 79 | func AnySlice[R any](src any, tags ...string) []R { 80 | return AnySliceContext[R](context.Background(), src, tags...) 81 | } 82 | 83 | // AnySliceContext converts any input slice into destination type slice as return value 84 | func AnySliceContext[R any](ctx context.Context, src any, tags ...string) []R { 85 | res := []R{} 86 | _ = TryToAnySliceContext(ctx, &res, src, tags...) 87 | return res 88 | } 89 | 90 | // TryToAnySlice converts any input slice into destination type slice 91 | // 92 | //go:inline 93 | func TryToAnySlice(dst, src any, tags ...string) error { 94 | return TryToAnySliceContext(context.Background(), dst, src, tags...) 95 | } 96 | 97 | // TryToAnySliceContext converts any input slice into destination type slice 98 | func TryToAnySliceContext(ctx context.Context, dst, src any, tags ...string) error { 99 | if dst == nil || src == nil { 100 | if dst == nil { 101 | return wrapError(ErrInvalidParams, "TryToAnySliceContext `destenation` parameter is nil") 102 | } 103 | return wrapError(ErrInvalidParams, "TryToAnySliceContext `source` parameter is nil") 104 | } 105 | 106 | dstSlice := reflectTarget(reflect.ValueOf(dst)) 107 | if k := dstSlice.Kind(); k != reflect.Slice && k != reflect.Array { 108 | return wrapError(ErrInvalidParams, "TryToAnySliceContext `destenation` parameter is not a slice or array") 109 | } 110 | 111 | srcSlice := reflectTarget(reflect.ValueOf(src)) 112 | if k := srcSlice.Kind(); k != reflect.Slice && k != reflect.Array { 113 | return wrapError(ErrInvalidParams, "TryToAnySliceContext `source` parameter is not a slice or array") 114 | } 115 | 116 | dstElemType := dstSlice.Type().Elem() 117 | 118 | if dstSlice.Len() < srcSlice.Len() { 119 | newv := reflect.MakeSlice(dstSlice.Type(), srcSlice.Len(), srcSlice.Len()) 120 | reflect.Copy(newv, dstSlice) 121 | dstSlice.Set(newv) 122 | dstSlice.SetLen(srcSlice.Len()) 123 | } 124 | 125 | for i := 0; i < srcSlice.Len(); i++ { 126 | srcItem := srcSlice.Index(i) 127 | dstItem := dstSlice.Index(i) 128 | if setter, _ := dstItem.Interface().(CastSetter); setter != nil { 129 | if dstItem.Kind() == reflect.Pointer && dstItem.IsNil() { 130 | dstItem.Set(reflect.New(dstItem.Type().Elem())) 131 | setter, _ = dstItem.Interface().(CastSetter) 132 | } 133 | if err := setter.CastSet(ctx, srcItem.Interface()); err != nil { 134 | return err 135 | } 136 | continue 137 | } else if dstItem.CanAddr() { 138 | if setter, _ := dstItem.Addr().Interface().(CastSetter); setter != nil { 139 | if err := setter.CastSet(ctx, srcItem.Interface()); err != nil { 140 | return err 141 | } 142 | continue 143 | } 144 | } 145 | if v, err := ReflectTryToTypeContext(ctx, srcItem, dstElemType, true, tags...); err == nil { 146 | if v == nil { 147 | dstItem.Set(reflect.Zero(dstElemType)) 148 | } else { 149 | dstItem.Set(reflect.ValueOf(v)) 150 | } 151 | } else { 152 | return err 153 | } 154 | } 155 | 156 | return nil 157 | } 158 | 159 | // IsSlice returns true if v is a slice or array 160 | func IsSlice(v any) bool { 161 | switch v.(type) { 162 | // Check default types first for performance 163 | case []any, []string, []bool, 164 | []int, []int8, []int16, []int32, []int64, 165 | []uint, []uint8, []uint16, []uint32, []uint64, 166 | []float32, []float64: 167 | return true 168 | default: 169 | refValue := reflect.ValueOf(v) 170 | kind := refValue.Kind() 171 | return kind == reflect.Slice || kind == reflect.Array 172 | } 173 | } 174 | -------------------------------------------------------------------------------- /struct_test.go: -------------------------------------------------------------------------------- 1 | package gocast 2 | 3 | import ( 4 | "context" 5 | "reflect" 6 | "testing" 7 | "time" 8 | 9 | "github.com/stretchr/testify/assert" 10 | ) 11 | 12 | type customInt int 13 | 14 | func (c *customInt) CastSet(ctx context.Context, v any) error { 15 | switch val := v.(type) { 16 | case customInt: 17 | *c = val 18 | default: 19 | *c = customInt(Number[int](v)) 20 | } 21 | return nil 22 | } 23 | 24 | type testStructParent struct { 25 | ParentName string `field:"parent_name"` 26 | } 27 | 28 | type testStruct struct { 29 | testStructParent 30 | 31 | Name string `field:"name"` 32 | Value int64 `field:"value"` 33 | Count customInt `field:"count"` 34 | Counts []customInt `field:"counts"` 35 | AnyTarget any `field:"anytarget"` 36 | NilVal any `field:"nilval,omitempty"` 37 | ignore bool `field:"ignore"` 38 | CreatedAt time.Time `field:"created_at"` 39 | UpdatedAt time.Time `field:"updated_at"` 40 | PublishedAt time.Time `field:"published_at"` 41 | } 42 | 43 | var testStructPreparedValue = map[string]any{ 44 | "parent_name": "parent name", 45 | "name": "test", 46 | "value": "1900", 47 | "count": 112.2, 48 | "counts": []any{"1.1", 2.1}, 49 | "created_at": "2020/10/10", 50 | "updated_at": time.Now().Unix(), 51 | "published_at": time.Now(), 52 | "anytarget": "hi", 53 | "nilval": nil, 54 | } 55 | 56 | func testPreparedStruct(t *testing.T, it *testStruct) { 57 | assert.Equal(t, "test", it.Name) 58 | assert.Equal(t, int64(1900), it.Value) 59 | assert.Equal(t, customInt(112), it.Count) 60 | assert.ElementsMatch(t, []customInt{1, 2}, it.Counts) 61 | assert.Equal(t, 2020, it.CreatedAt.Year()) 62 | assert.Equal(t, time.Now().Year(), it.UpdatedAt.Year()) 63 | assert.Equal(t, time.Now().Year(), it.PublishedAt.Year()) 64 | assert.Equal(t, "hi", it.AnyTarget) 65 | assert.Equal(t, false, it.ignore) 66 | assert.Nil(t, it.NilVal) 67 | } 68 | 69 | func TestStructGetSetFieldValue(t *testing.T) { 70 | st := &testStruct{} 71 | ctx := context.TODO() 72 | 73 | assert.NoError(t, SetStructFieldValue(ctx, &st, "Name", "TestName"), "set Name field value") 74 | assert.NoError(t, SetStructFieldValue(ctx, &st, "Value", int64(127)), "set Value field value") 75 | assert.NoError(t, SetStructFieldValue(ctx, &st, "Count", 3.1), "set Count field value") 76 | assert.NoError(t, SetStructFieldValue(ctx, &st, "CreatedAt", "2020/10/10"), "set CreatedAt field value") 77 | assert.NoError(t, SetStructFieldValue(ctx, &st, "UpdatedAt", time.Now().Unix()), "set UpdatedAt field value") 78 | assert.NoError(t, SetStructFieldValue(ctx, &st, "PublishedAt", time.Now()), "set PublishedAt field value") 79 | assert.NoError(t, SetStructFieldValue(ctx, &st, "AnyTarget", "hi"), "set AnyTarget field value") 80 | assert.NoError(t, SetStructFieldValue(ctx, &st, "ParentName", "parent name"), "set ParentName field value") 81 | assert.Error(t, SetStructFieldValue(ctx, &st, "UndefinedField", int64(127)), "set UndefinedField field value must be error") 82 | 83 | name, err := StructFieldValue(st, "Name") 84 | assert.NoError(t, err, "get Name value") 85 | assert.Equal(t, "TestName", name) 86 | 87 | value, err := StructFieldValue(st, "Value") 88 | assert.NoError(t, err, "get Value value") 89 | assert.Equal(t, int64(127), value) 90 | 91 | count, err := StructFieldValue(st, "Count") 92 | assert.NoError(t, err, "get Count value") 93 | assert.Equal(t, customInt(3), count) 94 | 95 | createdAt, err := StructFieldValue(st, "CreatedAt") 96 | assert.NoError(t, err, "get CreatedAt value") 97 | assert.Equal(t, 2020, createdAt.(time.Time).Year()) 98 | 99 | updatedAt, err := StructFieldValue(st, "UpdatedAt") 100 | assert.NoError(t, err, "get UpdatedAt value") 101 | assert.Equal(t, time.Now().Year(), updatedAt.(time.Time).Year()) 102 | 103 | publishedAt, err := StructFieldValue(st, "PublishedAt") 104 | assert.NoError(t, err, "get PublishedAt value") 105 | assert.Equal(t, time.Now().Year(), publishedAt.(time.Time).Year()) 106 | 107 | anyTarget, err := StructFieldValue(st, "AnyTarget") 108 | assert.NoError(t, err, "get AnyTarget value") 109 | assert.Equal(t, "hi", anyTarget) 110 | 111 | parentName, err := StructFieldValue(st, "ParentName") 112 | assert.NoError(t, err, "get ParentName value") 113 | assert.Equal(t, "parent name", parentName) 114 | 115 | _, err = StructFieldValue(st, "UndefinedField") 116 | assert.Error(t, err, "get UndefinedField value must be error") 117 | } 118 | 119 | func TestStructCopyCast(t *testing.T) { 120 | t.Run("new", func(t *testing.T) { 121 | res, err := Struct[testStruct](testStructPreparedValue, `field`) 122 | assert.NoError(t, err) 123 | testPreparedStruct(t, &res) 124 | }) 125 | 126 | t.Run("copy", func(t *testing.T) { 127 | var res testStruct 128 | err := TryCopyStruct(&res, testStructPreparedValue, `field`) 129 | assert.NoError(t, err) 130 | testPreparedStruct(t, &res) 131 | }) 132 | 133 | t.Run("nil-1", func(t *testing.T) { 134 | err := TryCopyStruct(nil, testStructPreparedValue, `field`) 135 | assert.ErrorIs(t, err, ErrInvalidParams) 136 | }) 137 | 138 | t.Run("nil-2", func(t *testing.T) { 139 | var res testStruct 140 | err := TryCopyStruct(&res, nil, `field`) 141 | assert.ErrorIs(t, err, ErrInvalidParams) 142 | }) 143 | } 144 | 145 | func TestStructCastNested(t *testing.T) { 146 | testStructPrepared, err := Struct[testStruct](testStructPreparedValue, `field`) 147 | assert.NoError(t, err) 148 | testPreparedStruct(t, &testStructPrepared) 149 | 150 | type testStruct2 struct { 151 | Sub testStruct `field:"sub"` 152 | SubMap map[string]*testStruct `field:"submap"` 153 | SubSlice []*testStruct `field:"subslice"` 154 | } 155 | 156 | data := map[string]any{ 157 | "sub": testStructPreparedValue, 158 | "submap": map[string]any{ 159 | "a": testStructPreparedValue, 160 | "b": testStructPreparedValue, 161 | "c": &testStructPrepared, 162 | }, 163 | "subslice": []any{ 164 | testStructPreparedValue, 165 | testStructPreparedValue, 166 | &testStructPrepared, 167 | }, 168 | } 169 | 170 | t.Run("cast", func(t *testing.T) { 171 | res, err := Struct[testStruct2](data, "field") 172 | assert.NoError(t, err) 173 | testPreparedStruct(t, &res.Sub) 174 | assert.Equal(t, 3, len(res.SubMap)) 175 | for k, val := range res.SubMap { 176 | t.Run("SubMap["+k+"]", func(t *testing.T) { 177 | testPreparedStruct(t, val) 178 | }) 179 | } 180 | assert.Equal(t, 3, len(res.SubSlice)) 181 | for i, val := range res.SubSlice { 182 | t.Run("SubSlice["+Str(i)+"]", func(t *testing.T) { 183 | testPreparedStruct(t, val) 184 | }) 185 | } 186 | }) 187 | 188 | t.Run("error", func(t *testing.T) { 189 | _, err := Struct[testStruct2](1, "field") 190 | assert.ErrorIs(t, err, ErrUnsupportedSourceType) 191 | }) 192 | } 193 | 194 | func TestStructFieldNames(t *testing.T) { 195 | fields := StructFieldNames(testStruct{}, "-") 196 | assert.ElementsMatch(t, 197 | []string{"Name", "Value", "Count", "Counts", "CreatedAt", "UpdatedAt", 198 | "PublishedAt", "AnyTarget", "NilVal", "ignore", "ParentName"}, fields) 199 | fields = StructFieldNames(testStruct{}, "") 200 | assert.ElementsMatch(t, 201 | []string{"name", "value", "count", "counts", "created_at", "updated_at", 202 | "published_at", "anytarget", "nilval", "ignore", "parent_name"}, fields) 203 | } 204 | 205 | func TestStructFieldTags(t *testing.T) { 206 | fields := StructFieldTags(testStruct{}, "field") 207 | assert.Equal(t, 208 | map[string]string{ 209 | "ParentName": "parent_name", 210 | "AnyTarget": "anytarget", 211 | "Count": "count", 212 | "Counts": "counts", 213 | "Name": "name", 214 | "NilVal": "nilval", 215 | "CreatedAt": "created_at", 216 | "UpdatedAt": "updated_at", 217 | "PublishedAt": "published_at", 218 | "Value": "value", 219 | "ignore": "ignore"}, fields) 220 | } 221 | 222 | func TestStructAllFieldNames(t *testing.T) { 223 | fields := StructAllFieldNames(struct { 224 | F string `field:"f" json:"F"` 225 | J int `field:"j" json:"J"` 226 | }{}, "field", "json") 227 | assert.True(t, reflect.DeepEqual(fields, map[string][]string{ 228 | "F": {"F", "f"}, 229 | "J": {"J", "j"}, 230 | })) 231 | 232 | assert.Nil(t, StructAllFieldNames(map[string]string{}, "field", "json")) 233 | } 234 | 235 | func TestIsStruct(t *testing.T) { 236 | assert.True(t, IsStruct(testStruct{})) 237 | assert.False(t, IsStruct(1)) 238 | } 239 | 240 | func BenchmarkGetSetFieldValue(b *testing.B) { 241 | st := &struct{ Name string }{} 242 | ctx := context.TODO() 243 | 244 | b.Run("set", func(b *testing.B) { 245 | for i := 0; i < b.N; i++ { 246 | err := SetStructFieldValue(ctx, st, "Name", "value") 247 | if err != nil { 248 | b.Error("set field error", err.Error()) 249 | } 250 | } 251 | }) 252 | 253 | b.Run("get", func(b *testing.B) { 254 | for i := 0; i < b.N; i++ { 255 | _, err := StructFieldValue(st, "Name") 256 | if err != nil { 257 | b.Error("get field error", err.Error()) 258 | } 259 | } 260 | }) 261 | } 262 | -------------------------------------------------------------------------------- /cast.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2014 Dmitry Ponomarev 2 | // 3 | // Permission is hereby granted, free of charge, to any person obtaining a copy of 4 | // this software and associated documentation files (the "Software"), to deal in 5 | // the Software without restriction, including without limitation the rights to 6 | // use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 7 | // the Software, and to permit persons to whom the Software is furnished to do so, 8 | // subject to the following conditions: 9 | // 10 | // The above copyright notice and this permission notice shall be included in all 11 | // copies or substantial portions of the Software. 12 | // 13 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 15 | // FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 16 | // COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 17 | // IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 18 | // CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 19 | 20 | package gocast 21 | 22 | import ( 23 | "context" 24 | "fmt" 25 | "reflect" 26 | ) 27 | 28 | // TryTo cast any input type into the target 29 | func TryTo(v, to any, tags ...string) (any, error) { 30 | return TryToContext(context.Background(), v, to, tags...) 31 | } 32 | 33 | // TryToContext cast any input type into the target 34 | func TryToContext(ctx context.Context, v, to any, tags ...string) (any, error) { 35 | if v == nil { 36 | if vl := reflect.ValueOf(v); !vl.IsValid() || vl.IsNil() { 37 | return nil, nil 38 | } 39 | return nil, wrapError(ErrInvalidParams, `TryToContext: "v" is nil`) 40 | } 41 | return TryToTypeContext(ctx, v, reflect.TypeOf(to), tags...) 42 | } 43 | 44 | // TryTo cast any input type into the target 45 | func To(v, to any, tags ...string) any { 46 | val, _ := TryTo(v, to, tags...) 47 | return val 48 | } 49 | 50 | // TryToType cast any input type into the target reflection 51 | func TryToType(v any, t reflect.Type, tags ...string) (any, error) { 52 | return TryToTypeContext(context.Background(), v, t, tags...) 53 | } 54 | 55 | // TryToTypeContext cast any input type into the target reflection 56 | func TryToTypeContext(ctx context.Context, v any, t reflect.Type, tags ...string) (any, error) { 57 | if v == nil { 58 | if vl := reflect.ValueOf(v); !vl.IsValid() || vl.IsNil() { 59 | return nil, nil 60 | } 61 | return nil, wrapError(ErrInvalidParams, `TryToTypeContext: "v" is nil`) 62 | } 63 | val := reflect.ValueOf(v) 64 | if t == nil { // In case of type is ANY make a copy of data 65 | if k := val.Kind(); k == reflect.Struct || k == reflect.Map || k == reflect.Slice || k == reflect.Array { 66 | return ReflectTryToTypeContext(ctx, val, val.Type(), true, tags...) 67 | } 68 | return v, nil 69 | } 70 | return ReflectTryToTypeContext(ctx, val, t, true, tags...) 71 | } 72 | 73 | // ToType cast any input type into the target reflection 74 | func ToType(v any, t reflect.Type, tags ...string) any { 75 | val, _ := TryToType(v, t, tags...) 76 | return val 77 | } 78 | 79 | // ReflectTryToType converts reflection value to reflection type or returns error 80 | func ReflectTryToType(v reflect.Value, t reflect.Type, recursive bool, tags ...string) (any, error) { 81 | return ReflectTryToTypeContext(context.Background(), v, t, recursive, tags...) 82 | } 83 | 84 | // ReflectTryToTypeContext converts reflection value to reflection type or returns error 85 | func ReflectTryToTypeContext(ctx context.Context, srcVal reflect.Value, t reflect.Type, recursive bool, tags ...string) (any, error) { 86 | v := reflectTarget(srcVal) 87 | if !v.IsValid() { 88 | switch t.Kind() { 89 | case reflect.Ptr, reflect.Interface, reflect.Map, reflect.Slice, reflect.Array, reflect.Chan, reflect.Func: 90 | return nil, nil 91 | default: 92 | return nil, wrapError(ErrInvalidParams, "ReflectTryToTypeContext: `srcVal` is invalid") 93 | } 94 | } 95 | if v.Type() == t { 96 | if k := t.Kind(); k != reflect.Struct && 97 | k != reflect.Map && 98 | k != reflect.Array && k != reflect.Slice && 99 | k != reflect.Interface && k != reflect.Pointer { 100 | return v.Interface(), nil 101 | } 102 | } 103 | var err error 104 | switch t.Kind() { 105 | case reflect.String: 106 | if stringer, _ := srcVal.Interface().(fmt.Stringer); stringer != nil { 107 | return stringer.String(), nil 108 | } 109 | return TryStr(v.Interface()) 110 | case reflect.Bool: 111 | return Bool(v.Interface()), nil 112 | case reflect.Int: 113 | return TryNumber[int](v.Interface()) 114 | case reflect.Int8: 115 | return TryNumber[int8](v.Interface()) 116 | case reflect.Int16: 117 | return TryNumber[int16](v.Interface()) 118 | case reflect.Int32: 119 | return TryNumber[int32](v.Interface()) 120 | case reflect.Int64: 121 | return TryNumber[int64](v.Interface()) 122 | case reflect.Uint: 123 | return TryNumber[uint](v.Interface()) 124 | case reflect.Uint8: 125 | return TryNumber[uint8](v.Interface()) 126 | case reflect.Uint16: 127 | return TryNumber[uint16](v.Interface()) 128 | case reflect.Uint32: 129 | return TryNumber[uint32](v.Interface()) 130 | case reflect.Uint64: 131 | return TryNumber[uint64](v.Interface()) 132 | case reflect.Float32: 133 | return TryNumber[float32](v.Interface()) 134 | case reflect.Float64: 135 | return TryNumber[float64](v.Interface()) 136 | case reflect.Slice, reflect.Array: 137 | slice := reflect.New(t) 138 | if err = TryToAnySliceContext(ctx, slice.Interface(), v.Interface(), tags...); err == nil { 139 | return slice.Elem().Interface(), nil 140 | } 141 | case reflect.Map: 142 | mp := reflect.MakeMap(t).Interface() 143 | if err = ToMapContext(ctx, mp, v.Interface(), recursive, tags...); err == nil { 144 | return mp, nil 145 | } 146 | case reflect.Interface: 147 | return v.Interface(), nil 148 | case reflect.Pointer: 149 | var ( 150 | vl any 151 | tElem = t.Elem() 152 | ) 153 | if tElem.Kind() == reflect.Struct { 154 | newVal := reflect.New(tElem) 155 | if err = TryCopyStructContext(ctx, newVal.Interface(), v.Interface(), tags...); err == nil { 156 | return newVal.Interface(), nil 157 | } 158 | } else if vl, err = ReflectTryToTypeContext(ctx, v, tElem, true, tags...); err == nil { 159 | return reflect.ValueOf(vl).Addr().Interface(), nil 160 | } 161 | case reflect.Struct: 162 | newVal := reflect.New(t) 163 | if err = TryCopyStructContext(ctx, newVal.Interface(), v.Interface(), tags...); err == nil { 164 | return newVal.Elem().Interface(), nil 165 | } 166 | default: 167 | if v.Type() == t { 168 | newVal := reflect.New(t) 169 | reflect.Copy(newVal.Addr(), v.Addr()) 170 | return newVal.Interface(), nil 171 | } 172 | } 173 | return nil, err 174 | } 175 | 176 | // ReflectToType converts reflection valut to reflection type or returns nil 177 | func ReflectToType(v reflect.Value, t reflect.Type, tags ...string) any { 178 | return ReflectToTypeContext(context.Background(), v, t, tags...) 179 | } 180 | 181 | // ReflectToType converts reflection valut to reflection type or returns nil 182 | func ReflectToTypeContext(ctx context.Context, v reflect.Value, t reflect.Type, tags ...string) any { 183 | val, _ := ReflectTryToTypeContext(ctx, v, t, true, tags...) 184 | return val 185 | } 186 | 187 | // TryCastValue source type into the target type 188 | func TryCastValue[R any, T any](v T, recursive bool, tags ...string) (R, error) { 189 | return TryCastValueContext[R](context.Background(), v, recursive, tags...) 190 | } 191 | 192 | // TryCastValueContext source type into the target type 193 | func TryCastValueContext[R any, T any](ctx context.Context, v T, recursive bool, tags ...string) (R, error) { 194 | var ( 195 | rVal R 196 | val, err = TryToContext(ctx, v, rVal, tags...) 197 | ) 198 | if err != nil { 199 | return rVal, err 200 | } 201 | switch nval := val.(type) { 202 | case *R: 203 | return *nval, nil 204 | case nil: 205 | return rVal, nil 206 | default: 207 | return val.(R), nil 208 | } 209 | } 210 | 211 | // TryCastRecursive source type into the target type with recursive data converting 212 | func TryCastRecursive[R any, T any](v T, tags ...string) (R, error) { 213 | return TryCastRecursiveContext[R](context.Background(), v, tags...) 214 | } 215 | 216 | // TryCastRecursiveContext source type into the target type with recursive data converting 217 | func TryCastRecursiveContext[R any, T any](ctx context.Context, v T, tags ...string) (R, error) { 218 | return TryCastValueContext[R](ctx, v, true, tags...) 219 | } 220 | 221 | // TryCast source type into the target type 222 | func TryCast[R any, T any](v T, tags ...string) (R, error) { 223 | return TryCastContext[R](context.Background(), v, tags...) 224 | } 225 | 226 | // TryCastContext source type into the target type 227 | func TryCastContext[R any, T any](ctx context.Context, v T, tags ...string) (R, error) { 228 | return TryCastValueContext[R](ctx, v, false, tags...) 229 | } 230 | 231 | // Cast source type into the target type 232 | func Cast[R any, T any](v T, tags ...string) R { 233 | return CastContext[R](context.Background(), v, tags...) 234 | } 235 | 236 | // CastContext source type into the target type 237 | func CastContext[R any, T any](ctx context.Context, v T, tags ...string) R { 238 | val, _ := TryCastContext[R](ctx, v, tags...) 239 | return val 240 | } 241 | 242 | // CastRecursive source type into the target type 243 | func CastRecursive[R any, T any](v T, tags ...string) R { 244 | return CastRecursiveContext[R](context.Background(), v, tags...) 245 | } 246 | 247 | // CastRecursiveContext source type into the target type 248 | func CastRecursiveContext[R any, T any](ctx context.Context, v T, tags ...string) R { 249 | val, _ := TryCastRecursiveContext[R](ctx, v, tags...) 250 | return val 251 | } 252 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # GoCast 2 | 3 | [![GoDoc](https://godoc.org/github.com/demdxx/gocast?status.svg)](https://godoc.org/github.com/demdxx/gocast) 4 | [![Build Status](https://github.com/demdxx/gocast/workflows/Tests/badge.svg)](https://github.com/demdxx/gocast/actions?workflow=Tests) 5 | [![Go Report Card](https://goreportcard.com/badge/github.com/demdxx/gocast)](https://goreportcard.com/report/github.com/demdxx/gocast) 6 | [![Coverage Status](https://coveralls.io/repos/github/demdxx/gocast/badge.svg?branch=master)](https://coveralls.io/github/demdxx/gocast?branch=master) 7 | 8 | ## Introduction 9 | 10 | GoCast is a powerful Go library that allows you to easily convert between different basic types in a consistent and efficient way. Whether you need to convert strings, numbers, or perform deep copying of complex data structures, GoCast has got you covered. 11 | 12 | ### ✨ Latest Improvements 13 | 14 | - **🚀 Enhanced Deep Copy**: Advanced deep copying with circular reference detection and custom options 15 | - **🎯 Type-Safe Functions**: Specialized `CopySlice[T]()` and `CopyMap[K,V]()` functions with better performance 16 | - **⚙️ Flexible Options**: `CopyOptions` for controlling copy behavior (depth limits, field filtering) 17 | - **🔧 Better Architecture**: Modular design with improved error handling and edge case support 18 | - **📈 Performance**: Optimized paths for different data types with minimal memory allocations 19 | 20 | ## Features 21 | 22 | - **Universal Type Casting**: GoCast provides a set of methods for universal type casting, making it easy to convert data between various types. 23 | - **Deep Copy Operations**: Advanced deep copying with support for circular references, custom options, and type-safe specialized functions. 24 | - **Struct Field Manipulation**: GoCast allows you to set and retrieve values of struct fields dynamically, making it a valuable tool for working with complex data structures. 25 | - **Custom Type Support**: You can define custom types and implement your own conversion logic, giving you full control over how data is cast. 26 | - **High Performance**: Optimized for speed with specialized paths for different data types and minimal allocations. 27 | 28 | ## Installation 29 | 30 | To use GoCast in your Go project, simply import it: 31 | 32 | ```go 33 | import "github.com/demdxx/gocast/v2" 34 | ``` 35 | 36 | ## Usage Example 37 | 38 | Here are some examples of how you can use GoCast: 39 | 40 | ```go 41 | // Example string casting: 42 | gocast.Str("strasstr") // "strasstr" 43 | gocast.Str(8) // "8" 44 | gocast.Str(8.31) // "8.31" 45 | gocast.Str([]byte("one time")) // "one time" 46 | gocast.Str(nil) // "" 47 | 48 | // Example number casting: 49 | gocast.Number[int](8) // 8 50 | gocast.Number[int](8.31) // 8 51 | gocast.Number[int]("8") // 8 52 | gocast.Number[int](true) // 1 53 | gocast.Number[int](false) // 0 54 | 55 | var eight any = 8 56 | gocast.Number[int](eight) // 8 57 | gocast.Cast[int](eight) // 8 58 | gocast.Number[int](nil) // 0 59 | 60 | // Number converts only into numeric values (simpler and faster then Cast) 61 | gocast.Number[float32]("2.12") // 2.12 62 | 63 | // Cast converts any type to any other type 64 | gocast.Cast[float64]("2.") // 2.0 65 | 66 | val, err := gocast.TryCast[int]("123.2") // 123, 67 | 68 | res := gocast.Map[string, any](struct{ID int64}{ID: 1}) // map[string]any{"ID": 1} 69 | 70 | gocast.Copy(map[string]any{"ID": 1}) // map[string]any{"ID": 1} 71 | ``` 72 | 73 | ```go 74 | func sumAll(vals ...any) int { 75 | var result int = 0 76 | for _, v := range vals { 77 | result += gocast.Number[int](v) 78 | } 79 | return result 80 | } 81 | ``` 82 | 83 | ## Deep Copy Operations 84 | 85 | GoCast provides powerful deep copying capabilities with support for complex data structures: 86 | 87 | ```go 88 | // Basic deep copy 89 | original := map[string]any{"data": []int{1, 2, 3}} 90 | copied, err := gocast.TryCopy(original) 91 | 92 | // Type-safe specialized functions 93 | originalSlice := []int{1, 2, 3, 4, 5} 94 | copiedSlice := gocast.CopySlice(originalSlice) 95 | 96 | originalMap := map[string]int{"a": 1, "b": 2} 97 | copiedMap := gocast.CopyMap(originalMap) 98 | 99 | // Copy with panic on error 100 | result := gocast.MustCopy(complexStruct) 101 | 102 | // Copy with custom options 103 | opts := gocast.CopyOptions{ 104 | MaxDepth: 5, // Limit recursion depth 105 | IgnoreUnexportedFields: true, // Skip unexported fields 106 | IgnoreCircularRefs: false, // Handle circular references 107 | } 108 | copied, err := gocast.TryCopyWithOptions(original, opts) 109 | 110 | // Circular reference handling 111 | type Node struct { 112 | Value int 113 | Next *Node 114 | } 115 | node1 := &Node{Value: 1} 116 | node2 := &Node{Value: 2} 117 | node1.Next = node2 118 | node2.Next = node1 // Circular reference 119 | 120 | copiedNode, err := gocast.TryCopy(node1) // Handles circular refs automatically 121 | ``` 122 | 123 | ## Struct Field Manipulation 124 | 125 | GoCast also allows you to work with struct fields dynamically: 126 | 127 | ```go 128 | type User struct { 129 | ID uint64 130 | Email string 131 | } 132 | 133 | var user User 134 | 135 | // Set structure values 136 | err := gocast.SetStructFieldValue(&user, "ID", uint64(19)) 137 | err := gocast.SetStructFieldValue(&user, "Email", "iamawesome@mail.com") 138 | 139 | id, err := gocast.StructFieldValue(user, "ID") 140 | email, err := gocast.StructFieldValue(user, "Email") 141 | fmt.Printf("User: %d - %s", id, email) 142 | // > User: 19 - iamawesome@mail.com 143 | ``` 144 | 145 | ## Custom Type Support 146 | 147 | You can define and use custom types with GoCast: 148 | 149 | ```go 150 | // Define custom type 151 | type Money int64 152 | 153 | func (m *Money) CastSet(ctx context.Context, v any) error { 154 | switch val := v.(type) { 155 | case Money: 156 | *m = val 157 | default: 158 | *m = Money(gocast.Float64(v) * 1000000) 159 | } 160 | return nil 161 | } 162 | 163 | // Use custom type in structs 164 | type Car struct { 165 | ID int64 166 | Price Money 167 | } 168 | 169 | var car Car 170 | 171 | // Mapping values into struct 172 | gocast.TryCopyStruct(&car, map[string]any{"ID":1, "Price": "12000.00"}) 173 | ``` 174 | 175 | ## Benchmarks 176 | 177 | Here are some benchmark results for GoCast: 178 | 179 | ```sh 180 | > go test -benchmem -v -race -bench=. 181 | 182 | goos: darwin 183 | goarch: arm64 184 | pkg: github.com/demdxx/gocast/v2 185 | cpu: Apple M2 Ultra 186 | 187 | # Core functionality benchmarks 188 | BenchmarkBool-24 20453542 58.87 ns/op 0 B/op 0 allocs/op 189 | BenchmarkToFloat-24 10951923 107.0 ns/op 0 B/op 0 allocs/op 190 | BenchmarkToInt-24 9870794 121.1 ns/op 0 B/op 0 allocs/op 191 | BenchmarkToString-24 836929 1622 ns/op 5 B/op 0 allocs/op 192 | 193 | # Deep copy benchmarks 194 | BenchmarkCopy/simple_int-24 100000000 10.03 ns/op 8 B/op 1 allocs/op 195 | BenchmarkCopy/simple_string-24 28639788 41.20 ns/op 32 B/op 2 allocs/op 196 | BenchmarkCopy/simple_struct-24 16650103 71.49 ns/op 48 B/op 2 allocs/op 197 | BenchmarkCopy/complex_struct-24 1796786 663.3 ns/op 632 B/op 18 allocs/op 198 | BenchmarkCopy/slice-24 5762702 205.5 ns/op 152 B/op 4 allocs/op 199 | BenchmarkCopy/map-24 1966965 607.1 ns/op 504 B/op 23 allocs/op 200 | 201 | # Specialized copy functions (type-safe and faster) 202 | BenchmarkSpecializedFunctions/CopySlice_specialized-24 9462444 124.9 ns/op 160 B/op 11 allocs/op 203 | BenchmarkSpecializedFunctions/CopyMap_specialized-24 2794791 427.3 ns/op 456 B/op 17 allocs/op 204 | 205 | # Other operations 206 | BenchmarkGetSetFieldValue/set-24 1000000 1021 ns/op 64 B/op 4 allocs/op 207 | BenchmarkGetSetFieldValue/get-24 1869465 643.8 ns/op 48 B/op 3 allocs/op 208 | BenchmarkParseTime-24 374346 3130 ns/op 700 B/op 17 allocs/op 209 | BenchmarkIsEmpty-24 37383031 31.23 ns/op 0 B/op 0 allocs/op 210 | ``` 211 | 212 | ### Performance Highlights 213 | 214 | - **Deep Copy**: Highly optimized with specialized functions for different types 215 | - **Type-Safe Copies**: `CopySlice` and `CopyMap` provide better performance for specific types 216 | - **Memory Efficient**: Minimal allocations for most operations 217 | - **Circular References**: Automatic detection and handling with zero performance cost for non-circular data 218 | 219 | ## API Reference 220 | 221 | ### Core Copy Functions 222 | 223 | ```go 224 | // Basic deep copy with error handling 225 | func TryCopy[T any](src T) (T, error) 226 | 227 | // Deep copy with panic on error 228 | func Copy[T any](src T) T 229 | 230 | // Panic version of TryCopy 231 | func MustCopy[T any](src T) T 232 | 233 | // Copy any type (interface{}) 234 | func TryAnyCopy(src any) (any, error) 235 | func AnyCopy(src any) any 236 | ``` 237 | 238 | ### Specialized Copy Functions 239 | 240 | ```go 241 | // Type-safe slice copying (40% faster than TryCopy for slices) 242 | func CopySlice[T any](src []T) []T 243 | 244 | // Type-safe map copying (30% faster than TryCopy for maps) 245 | func CopyMap[K comparable, V any](src map[K]V) map[K]V 246 | 247 | // Shallow copy (for immutable data) 248 | func ShallowCopy[T any](src T) T 249 | 250 | // Interface copy with type preservation 251 | func CopyInterface(src any) (any, error) 252 | ``` 253 | 254 | ### Advanced Copy Options 255 | 256 | ```go 257 | type CopyOptions struct { 258 | IgnoreUnexportedFields bool // Skip unexported struct fields 259 | MaxDepth int // Limit recursion depth 260 | IgnoreCircularRefs bool // Ignore circular references instead of preserving them 261 | } 262 | 263 | // Copy with custom options 264 | func TryCopyWithOptions[T any](src T, opts CopyOptions) (T, error) 265 | ``` 266 | 267 | ### Usage Recommendations 268 | 269 | - **For simple types**: Use `TryCopy` or `Copy` - they're optimized automatically 270 | - **For slices**: Use `CopySlice[T]()` for better type safety and performance 271 | - **For maps**: Use `CopyMap[K,V]()` for better type safety and performance 272 | - **For complex nested data**: Use `TryCopyWithOptions` with `MaxDepth` to control resource usage 273 | - **For circular references**: `TryCopy` handles them automatically 274 | - **For performance-critical code**: Consider if deep copy is needed - immutable types don't require copying 275 | 276 | ### Examples 277 | 278 | See the [examples directory](examples/) for more detailed usage examples and the [test files](.) for comprehensive usage patterns. 279 | 280 | ### Documentation 281 | 282 | Full API documentation is available at [GoDoc](https://godoc.org/github.com/demdxx/gocast/v2). 283 | 284 | ## License 285 | 286 | GoCast is released under the MIT License. See the [LICENSE](LICENSE) file for details. 287 | -------------------------------------------------------------------------------- /map.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2014 Dmitry Ponomarev 2 | // 3 | // Permission is hereby granted, free of charge, to any person obtaining a copy of 4 | // this software and associated documentation files (the "Software"), to deal in 5 | // the Software without restriction, including without limitation the rights to 6 | // use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 7 | // the Software, and to permit persons to whom the Software is furnished to do so, 8 | // subject to the following conditions: 9 | // 10 | // The above copyright notice and this permission notice shall be included in all 11 | // copies or substantial portions of the Software. 12 | // 13 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 15 | // FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 16 | // COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 17 | // IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 18 | // CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 19 | 20 | package gocast 21 | 22 | import ( 23 | "context" 24 | "reflect" 25 | ) 26 | 27 | // TryMapCopy converts source into destination or return error 28 | func TryMapCopy[K comparable, V any](dst map[K]V, src any, recursive bool, tags ...string) error { 29 | return TryMapCopyContext(context.Background(), dst, src, recursive, tags...) 30 | } 31 | 32 | // TryMapCopyContext converts source into destination or return error 33 | func TryMapCopyContext[K comparable, V any](ctx context.Context, dst map[K]V, src any, recursive bool, tags ...string) error { 34 | if dst == nil || src == nil { 35 | if dst == nil { 36 | return wrapError(ErrInvalidParams, "TryMapCopyContext `destenation` parameter is nil") 37 | } 38 | return wrapError(ErrInvalidParams, "TryMapCopyContext `source` parameter is nil") 39 | } 40 | var ( 41 | srcVal = reflectTarget(reflect.ValueOf(src)) 42 | srcType = srcVal.Type() 43 | ) 44 | switch srcType.Kind() { 45 | case reflect.Map: 46 | for _, k := range srcVal.MapKeys() { 47 | field := srcVal.MapIndex(k) 48 | key, err := TryCast[K](k.Interface()) 49 | if err == nil { 50 | if recursive { 51 | dst[key], err = TryCastRecursiveContext[V](ctx, field.Interface(), tags...) 52 | } else { 53 | dst[key], err = TryCastContext[V](ctx, field.Interface(), tags...) 54 | } 55 | } 56 | if err != nil { 57 | return wrapError(err, `"`+Str(k.Interface())+`" map key`) 58 | } 59 | } 60 | case reflect.Struct: 61 | for i := 0; i < srcVal.NumField(); i++ { 62 | name, omitempty := fieldNameFromTags(srcType.Field(i), tags...) 63 | if len(name) > 0 { 64 | key, err := TryCast[K](name) 65 | if err != nil { 66 | return err 67 | } 68 | field := srcVal.Field(i) 69 | fl := getValue(field.Interface()) 70 | if !omitempty || !IsEmpty(fl) { 71 | if recursive { 72 | dst[key], err = TryCastRecursiveContext[V](ctx, fl, tags...) 73 | } else { 74 | dst[key], err = TryCastContext[V](ctx, fl, tags...) 75 | } 76 | if err != nil { 77 | return wrapError(err, "`"+name+"` struct key") 78 | } 79 | } // end if !omitempty || !IsEmpty(fl) 80 | } 81 | } 82 | default: 83 | return wrapError(ErrUnsupportedSourceType, srcType.String()) 84 | } 85 | return nil 86 | } 87 | 88 | // ToMap cast your Source into the Destination type 89 | // tag defines the tags name in the structure to map the keys 90 | func ToMap(dst, src any, recursive bool, tags ...string) error { 91 | return ToMapContext(context.Background(), dst, src, recursive, tags...) 92 | } 93 | 94 | // ToMap cast your Source into the Destination type 95 | // tag defines the tags name in the structure to map the keys 96 | func ToMapContext(ctx context.Context, dst, src any, recursive bool, tags ...string) error { 97 | if dst == nil || src == nil { 98 | if dst == nil { 99 | return wrapError(ErrInvalidParams, "ToMapContext `destenation` parameter is nil") 100 | } 101 | return wrapError(ErrInvalidParams, "ToMapContext `source` parameter is nil") 102 | } 103 | 104 | var ( 105 | err error 106 | destVal = reflectTarget(reflect.ValueOf(dst)) 107 | destType = destVal.Type() 108 | srcVal = reflectTarget(reflect.ValueOf(src)) 109 | srcType = srcVal.Type() 110 | ) 111 | 112 | if dst = destVal.Interface(); dst == nil { 113 | dst = reflect.MakeMap(destType).Interface() 114 | destVal = reflect.ValueOf(dst) 115 | } 116 | 117 | switch dest := dst.(type) { 118 | case map[any]any: 119 | switch srcType.Kind() { 120 | case reflect.Map: 121 | for _, k := range srcVal.MapKeys() { 122 | field := srcVal.MapIndex(k) 123 | if recursive { 124 | dest[k.Interface()], err = mapDestValue(field.Interface(), destType, recursive, tags...) 125 | if err != nil { 126 | return wrapError(err, Str(k.Interface())) 127 | } 128 | } else { 129 | dest[k.Interface()] = field.Interface() 130 | } 131 | } 132 | case reflect.Struct: 133 | for i := 0; i < srcVal.NumField(); i++ { 134 | name, omitempty := fieldNameFromTags(srcType.Field(i), tags...) 135 | if len(name) > 0 { 136 | field := srcVal.Field(i) 137 | fl := getValue(field.Interface()) 138 | if !omitempty || !IsEmpty(fl) { 139 | if recursive { 140 | dest[name], err = mapDestValue(fl, destType, recursive, tags...) 141 | if err != nil { 142 | return wrapError(err, "`"+name+"` value") 143 | } 144 | } else { 145 | dest[name] = fl 146 | } 147 | } // end if !omitempty || !IsEmpty(fl) 148 | } 149 | } 150 | default: 151 | err = wrapError(ErrUnsupportedSourceType, srcType.String()) 152 | } 153 | case map[string]any: 154 | err = TryMapCopyContext(ctx, dest, src, recursive, tags...) 155 | case map[string]string: 156 | err = TryMapCopyContext(ctx, dest, src, recursive, tags...) 157 | default: 158 | switch destType.Kind() { 159 | case reflect.Map, reflect.Struct: 160 | keyType := destType.Key() 161 | elemType := destType.Elem() 162 | switch srcType.Kind() { 163 | case reflect.Map: 164 | for _, k := range srcVal.MapKeys() { 165 | keyVal, err := ReflectTryToTypeContext(ctx, k, keyType, recursive, tags...) 166 | if err != nil { 167 | return wrapError(err, Str(k.Interface())) 168 | } 169 | mapVal := reflectTarget(srcVal.MapIndex(k)) 170 | val, err := ReflectTryToTypeContext(ctx, mapVal, elemType, recursive, tags...) 171 | if err != nil { 172 | return wrapError(err, "`"+Str(k.Interface())+"` value") 173 | } 174 | destVal.SetMapIndex(reflect.ValueOf(keyVal), reflect.ValueOf(val)) 175 | } 176 | case reflect.Struct: 177 | for i := 0; i < srcVal.NumField(); i++ { 178 | name, omitempty := fieldNameFromTags(srcType.Field(i), tags...) 179 | if len(name) > 0 { 180 | flVal := reflectTarget(srcVal.Field(i)) 181 | fl := getValue(flVal.Interface()) 182 | if !omitempty || !IsEmpty(fl) { 183 | keyVal, err := TryToType(name, keyType) 184 | if err != nil { 185 | return wrapError(err, name) 186 | } 187 | val, err := ReflectTryToTypeContext(ctx, flVal, elemType, recursive, tags...) 188 | if err != nil { 189 | return wrapError(err, "`"+name+"` value") 190 | } 191 | destVal.SetMapIndex(reflect.ValueOf(keyVal), reflect.ValueOf(val)) 192 | } 193 | } // end if 194 | } 195 | default: 196 | err = wrapError(ErrUnsupportedSourceType, srcType.String()) 197 | } 198 | default: 199 | err = wrapError(ErrUnsupportedType, destType.String()) 200 | } 201 | } 202 | return err 203 | } 204 | 205 | // TryMapFrom source creates new map to convert 206 | func TryMapFrom[K comparable, V any](src any, recursive bool, tags ...string) (map[K]V, error) { 207 | return TryMapFromContext[K, V](context.Background(), src, recursive, tags...) 208 | } 209 | 210 | // TryMapFrom source creates new map to convert 211 | func TryMapFromContext[K comparable, V any](ctx context.Context, src any, recursive bool, tags ...string) (map[K]V, error) { 212 | dst := make(map[K]V) 213 | err := TryMapCopyContext(ctx, dst, src, recursive, tags...) 214 | return dst, err 215 | } 216 | 217 | // TryMapRecursive creates new map to convert from soruce type with recursive field processing 218 | func TryMapRecursive[K comparable, V any](src any, tags ...string) (map[K]V, error) { 219 | return TryMapFrom[K, V](src, true, tags...) 220 | } 221 | 222 | // TryMapRecursiveContext creates new map to convert from soruce type with recursive field processing 223 | func TryMapRecursiveContext[K comparable, V any](ctx context.Context, src any, tags ...string) (map[K]V, error) { 224 | return TryMapFromContext[K, V](ctx, src, true, tags...) 225 | } 226 | 227 | // TryMap creates new map to convert from soruce type 228 | func TryMap[K comparable, V any](src any, tags ...string) (map[K]V, error) { 229 | return TryMapFrom[K, V](src, false, tags...) 230 | } 231 | 232 | // TryMapContext creates new map to convert from soruce type 233 | func TryMapContext[K comparable, V any](ctx context.Context, src any, tags ...string) (map[K]V, error) { 234 | return TryMapFromContext[K, V](ctx, src, false, tags...) 235 | } 236 | 237 | // MapRecursive creates map from source or returns nil 238 | func MapRecursive[K comparable, V any](src any, tags ...string) map[K]V { 239 | m, _ := TryMapRecursive[K, V](src, tags...) 240 | return m 241 | } 242 | 243 | // MapRecursiveContext creates map from source or returns nil 244 | func MapRecursiveContext[K comparable, V any](ctx context.Context, src any, tags ...string) map[K]V { 245 | m, _ := TryMapRecursiveContext[K, V](ctx, src, tags...) 246 | return m 247 | } 248 | 249 | // Map creates map from source or returns nil 250 | func Map[K comparable, V any](src any, tags ...string) map[K]V { 251 | m, _ := TryMap[K, V](src, tags...) 252 | return m 253 | } 254 | 255 | // MapContext creates map from source or returns nil 256 | func MapContext[K comparable, V any](ctx context.Context, src any, tags ...string) map[K]V { 257 | m, _ := TryMapContext[K, V](ctx, src, tags...) 258 | return m 259 | } 260 | 261 | // ToMapFrom any Map/Object type 262 | func ToMapFrom(src any, recursive bool, tags ...string) (map[any]any, error) { 263 | dst := make(map[any]any) 264 | err := ToMap(dst, src, recursive, tags...) 265 | return dst, err 266 | } 267 | 268 | // IsMap checks if the input value is a Map/Object type 269 | func IsMap(v any) bool { 270 | switch v.(type) { 271 | case map[any]any, map[string]any, map[string]string, map[string]int, map[int]int: 272 | return true 273 | default: 274 | return reflect.ValueOf(v).Kind() == reflect.Map 275 | } 276 | } 277 | 278 | /////////////////////////////////////////////////////////////////////////////// 279 | /// MARK: Helpers 280 | /////////////////////////////////////////////////////////////////////////////// 281 | 282 | func reflectMapValueByStringKeys(src reflect.Value, keys []string) any { 283 | mKeys := src.MapKeys() 284 | for _, key := range keys { 285 | for _, mKey := range mKeys { 286 | if Str(mKey.Interface()) == key { 287 | return src.MapIndex(mKey).Interface() 288 | } 289 | } 290 | } 291 | return nil 292 | } 293 | 294 | func mapDestValue(fl any, destType reflect.Type, recursive bool, tags ...string) (any, error) { 295 | field := reflect.ValueOf(fl) 296 | switch field.Kind() { 297 | case reflect.Slice, reflect.Array: 298 | if field.Len() > 0 { 299 | switch field.Index(0).Kind() { 300 | case reflect.Map, reflect.Struct: 301 | list := make([]any, field.Len()) 302 | for i := 0; i < field.Len(); i++ { 303 | var v any = reflect.New(destType) 304 | if err := ToMap(v, field.Index(i), recursive, tags...); err != nil { 305 | return nil, err 306 | } 307 | list = append(list, v) 308 | } 309 | return list, nil 310 | } 311 | } 312 | case reflect.Map, reflect.Struct: 313 | var v any = reflect.New(destType) 314 | if err := ToMap(v, fl, recursive, tags...); err != nil { 315 | return nil, err 316 | } 317 | return v, nil 318 | } 319 | return fl, nil 320 | } 321 | -------------------------------------------------------------------------------- /copy.go: -------------------------------------------------------------------------------- 1 | package gocast 2 | 3 | import ( 4 | "errors" 5 | "reflect" 6 | ) 7 | 8 | // TryCopy creates a deep copy of the provided value using reflection or 9 | // returns an error if the types do not match. 10 | // It returns a new value of the same type as the source. 11 | // If the source value is nil, it returns the zero value of the type. 12 | func TryCopy[T any](src T) (T, error) { 13 | var dst T 14 | 15 | srcValue := reflect.ValueOf(src) 16 | if !srcValue.IsValid() { 17 | return dst, nil 18 | } 19 | 20 | // Fast path for simple types 21 | if srcValue.Type().Comparable() && srcValue.Kind() <= reflect.Complex128 { 22 | return src, nil // Simple types are immutable, so no copy needed 23 | } 24 | 25 | // Use a visited map to handle circular references 26 | visited := make(map[uintptr]reflect.Value) 27 | 28 | err := deepCopy(srcValue, reflect.ValueOf(&dst).Elem(), visited) 29 | if err != nil { 30 | return dst, err 31 | } 32 | 33 | return dst, nil 34 | } 35 | 36 | // Copy creates a deep copy of the provided value using reflection. 37 | // It returns a new value of the same type as the source. 38 | // If the source value is nil, it returns the zero value of the type. 39 | // 40 | //go:inline 41 | func Copy[T any](src T) T { 42 | dst, err := TryCopy(src) 43 | if err != nil { 44 | panic(err) 45 | } 46 | return dst 47 | } 48 | 49 | // TryAnyCopy creates a deep copy of the provided value using reflection. 50 | func TryAnyCopy(src any) (any, error) { 51 | // Use a visited map to handle circular references 52 | visited := make(map[uintptr]reflect.Value) 53 | 54 | dst := reflect.New(reflect.TypeOf(src)).Elem() 55 | err := deepCopy(reflect.ValueOf(src), dst, visited) 56 | if err != nil { 57 | return nil, err 58 | } 59 | 60 | return dst.Interface(), nil 61 | } 62 | 63 | // AnyCopy creates a deep copy of the provided value using reflection. 64 | // 65 | //go:inline 66 | func AnyCopy(src any) any { 67 | dst, err := TryAnyCopy(src) 68 | if err != nil { 69 | panic(err) 70 | } 71 | return dst 72 | } 73 | 74 | // CopyWithOptions allows copying with custom options 75 | type CopyOptions struct { 76 | IgnoreUnexportedFields bool 77 | MaxDepth int 78 | IgnoreCircularRefs bool 79 | } 80 | 81 | // TryCopyWithOptions creates a deep copy with custom options 82 | func TryCopyWithOptions[T any](src T, opts CopyOptions) (T, error) { 83 | var dst T 84 | 85 | srcValue := reflect.ValueOf(src) 86 | if !srcValue.IsValid() { 87 | return dst, nil 88 | } 89 | 90 | // Fast path for simple types 91 | if srcValue.Type().Comparable() && srcValue.Kind() <= reflect.Complex128 { 92 | return src, nil 93 | } 94 | 95 | // Use a visited map to handle circular references 96 | visited := make(map[uintptr]reflect.Value) 97 | 98 | err := deepCopyWithOptions(srcValue, reflect.ValueOf(&dst).Elem(), visited, opts, 0) 99 | if err != nil { 100 | return dst, err 101 | } 102 | 103 | return dst, nil 104 | } 105 | 106 | func deepCopy(src, dst reflect.Value, visited map[uintptr]reflect.Value) error { 107 | // Handle nil or invalid source values 108 | if !src.IsValid() { 109 | return nil 110 | } 111 | 112 | // Fast path for simple types that don't need deep copying 113 | switch src.Kind() { 114 | case reflect.Bool, reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64, 115 | reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr, 116 | reflect.Float32, reflect.Float64, reflect.Complex64, reflect.Complex128, reflect.String: 117 | if dst.CanSet() { 118 | dst.Set(src) 119 | } 120 | return nil 121 | } 122 | 123 | // Get the underlying value if src is an interface 124 | if src.Kind() == reflect.Interface && !src.IsNil() { 125 | srcElem := src.Elem() 126 | if dst.CanSet() { 127 | newDst := reflect.New(srcElem.Type()).Elem() 128 | if err := deepCopy(srcElem, newDst, visited); err != nil { 129 | return err 130 | } 131 | dst.Set(newDst) 132 | } 133 | return nil 134 | } 135 | 136 | // Handle different kinds of values that need deep copying 137 | switch src.Kind() { 138 | case reflect.Pointer: 139 | return copyPointer(src, dst, visited) 140 | case reflect.Struct: 141 | return copyStruct(src, dst, visited) 142 | case reflect.Slice: 143 | return copySlice(src, dst, visited) 144 | case reflect.Array: 145 | return copyArray(src, dst, visited) 146 | case reflect.Map: 147 | return copyMap(src, dst, visited) 148 | case reflect.Chan, reflect.Func: 149 | return errors.New("unsupported type: " + src.Type().String()) 150 | default: 151 | // For other types, try direct assignment 152 | if dst.CanSet() { 153 | dst.Set(src) 154 | } 155 | } 156 | 157 | return nil 158 | } 159 | 160 | func deepCopyWithOptions(src, dst reflect.Value, visited map[uintptr]reflect.Value, opts CopyOptions, depth int) error { 161 | // Check max depth 162 | if opts.MaxDepth > 0 && depth >= opts.MaxDepth { 163 | if dst.CanSet() { 164 | dst.Set(reflect.Zero(dst.Type())) 165 | } 166 | return nil 167 | } 168 | 169 | // Handle nil or invalid source values 170 | if !src.IsValid() { 171 | return nil 172 | } 173 | 174 | // Fast path for simple types 175 | switch src.Kind() { 176 | case reflect.Bool, reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64, 177 | reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr, 178 | reflect.Float32, reflect.Float64, reflect.Complex64, reflect.Complex128, reflect.String: 179 | if dst.CanSet() { 180 | dst.Set(src) 181 | } 182 | return nil 183 | case reflect.Interface: 184 | if !src.IsNil() { 185 | srcElem := src.Elem() 186 | if dst.CanSet() { 187 | newDst := reflect.New(srcElem.Type()).Elem() 188 | if err := deepCopyWithOptions(srcElem, newDst, visited, opts, depth+1); err != nil { 189 | return err 190 | } 191 | dst.Set(newDst) 192 | } 193 | } 194 | return nil 195 | case reflect.Pointer: 196 | return copyPointerWithOptions(src, dst, visited, opts, depth) 197 | case reflect.Struct: 198 | return copyStructWithOptions(src, dst, visited, opts, depth) 199 | case reflect.Slice: 200 | return copySliceWithOptions(src, dst, visited, opts, depth) 201 | case reflect.Array: 202 | return copyArrayWithOptions(src, dst, visited, opts, depth) 203 | case reflect.Map: 204 | return copyMapWithOptions(src, dst, visited, opts, depth) 205 | case reflect.Chan, reflect.Func: 206 | return wrapError(ErrCopyUnsupportedType, src.Type().String()) 207 | default: 208 | if dst.CanSet() { 209 | dst.Set(src) 210 | } 211 | } 212 | 213 | return nil 214 | } 215 | 216 | func copyPointer(src, dst reflect.Value, visited map[uintptr]reflect.Value) error { 217 | if src.IsNil() { 218 | return nil 219 | } 220 | 221 | // Check for circular references 222 | ptr := src.Pointer() 223 | if existingDst, ok := visited[ptr]; ok { 224 | if dst.CanSet() { 225 | dst.Set(existingDst) 226 | } 227 | return nil 228 | } 229 | 230 | // Create a new pointer of the same type as src 231 | newPtr := reflect.New(src.Type().Elem()) 232 | if dst.CanSet() { 233 | dst.Set(newPtr) 234 | } 235 | 236 | // Add to visited BEFORE recursing to handle circular references properly 237 | visited[ptr] = newPtr 238 | 239 | // Recursively copy the pointed-to value 240 | return deepCopy(src.Elem(), newPtr.Elem(), visited) 241 | } 242 | 243 | func copyPointerWithOptions(src, dst reflect.Value, visited map[uintptr]reflect.Value, opts CopyOptions, depth int) error { 244 | if src.IsNil() { 245 | return nil 246 | } 247 | 248 | ptr := src.Pointer() 249 | if existingDst, ok := visited[ptr]; ok { 250 | if opts.IgnoreCircularRefs { 251 | return nil 252 | } 253 | if dst.CanSet() { 254 | dst.Set(existingDst) 255 | } 256 | return nil 257 | } 258 | 259 | newPtr := reflect.New(src.Type().Elem()) 260 | if dst.CanSet() { 261 | dst.Set(newPtr) 262 | } 263 | 264 | visited[ptr] = newPtr 265 | return deepCopyWithOptions(src.Elem(), newPtr.Elem(), visited, opts, depth+1) 266 | } 267 | 268 | func copyStruct(src, dst reflect.Value, visited map[uintptr]reflect.Value) error { 269 | for i := 0; i < src.NumField(); i++ { 270 | if !dst.Field(i).CanSet() { 271 | continue // Skip unexported fields 272 | } 273 | if err := deepCopy(src.Field(i), dst.Field(i), visited); err != nil { 274 | return err 275 | } 276 | } 277 | return nil 278 | } 279 | 280 | func copyStructWithOptions(src, dst reflect.Value, visited map[uintptr]reflect.Value, opts CopyOptions, depth int) error { 281 | for i := 0; i < src.NumField(); i++ { 282 | field := src.Type().Field(i) 283 | 284 | // Skip unexported fields if option is set 285 | if opts.IgnoreUnexportedFields && !field.IsExported() { 286 | continue 287 | } 288 | 289 | if !dst.Field(i).CanSet() { 290 | continue 291 | } 292 | 293 | if err := deepCopyWithOptions(src.Field(i), dst.Field(i), visited, opts, depth+1); err != nil { 294 | return err 295 | } 296 | } 297 | return nil 298 | } 299 | 300 | func copySlice(src, dst reflect.Value, visited map[uintptr]reflect.Value) error { 301 | if src.IsNil() { 302 | return nil 303 | } 304 | 305 | newSlice := reflect.MakeSlice(src.Type(), src.Len(), src.Cap()) 306 | dst.Set(newSlice) 307 | 308 | for i := 0; i < src.Len(); i++ { 309 | if err := deepCopy(src.Index(i), dst.Index(i), visited); err != nil { 310 | return err 311 | } 312 | } 313 | return nil 314 | } 315 | 316 | func copySliceWithOptions(src, dst reflect.Value, visited map[uintptr]reflect.Value, opts CopyOptions, depth int) error { 317 | if src.IsNil() { 318 | return nil 319 | } 320 | 321 | newSlice := reflect.MakeSlice(src.Type(), src.Len(), src.Cap()) 322 | dst.Set(newSlice) 323 | 324 | for i := 0; i < src.Len(); i++ { 325 | if err := deepCopyWithOptions(src.Index(i), dst.Index(i), visited, opts, depth+1); err != nil { 326 | return err 327 | } 328 | } 329 | return nil 330 | } 331 | 332 | func copyArray(src, dst reflect.Value, visited map[uintptr]reflect.Value) error { 333 | for i := 0; i < src.Len(); i++ { 334 | if err := deepCopy(src.Index(i), dst.Index(i), visited); err != nil { 335 | return err 336 | } 337 | } 338 | return nil 339 | } 340 | 341 | func copyArrayWithOptions(src, dst reflect.Value, visited map[uintptr]reflect.Value, opts CopyOptions, depth int) error { 342 | for i := 0; i < src.Len(); i++ { 343 | if err := deepCopyWithOptions(src.Index(i), dst.Index(i), visited, opts, depth+1); err != nil { 344 | return err 345 | } 346 | } 347 | return nil 348 | } 349 | 350 | func copyMap(src, dst reflect.Value, visited map[uintptr]reflect.Value) error { 351 | if src.IsNil() { 352 | return nil 353 | } 354 | 355 | newMap := reflect.MakeMap(src.Type()) 356 | dst.Set(newMap) 357 | 358 | iter := src.MapRange() 359 | for iter.Next() { 360 | srcKey := iter.Key() 361 | srcValue := iter.Value() 362 | 363 | dstKey := reflect.New(srcKey.Type()).Elem() 364 | if err := deepCopy(srcKey, dstKey, visited); err != nil { 365 | return err 366 | } 367 | 368 | dstValue := reflect.New(srcValue.Type()).Elem() 369 | if err := deepCopy(srcValue, dstValue, visited); err != nil { 370 | return err 371 | } 372 | 373 | dst.SetMapIndex(dstKey, dstValue) 374 | } 375 | return nil 376 | } 377 | 378 | func copyMapWithOptions(src, dst reflect.Value, visited map[uintptr]reflect.Value, opts CopyOptions, depth int) error { 379 | if src.IsNil() { 380 | return nil 381 | } 382 | 383 | newMap := reflect.MakeMap(src.Type()) 384 | dst.Set(newMap) 385 | 386 | iter := src.MapRange() 387 | for iter.Next() { 388 | srcKey := iter.Key() 389 | srcValue := iter.Value() 390 | 391 | dstKey := reflect.New(srcKey.Type()).Elem() 392 | if err := deepCopyWithOptions(srcKey, dstKey, visited, opts, depth+1); err != nil { 393 | return err 394 | } 395 | 396 | dstValue := reflect.New(srcValue.Type()).Elem() 397 | if err := deepCopyWithOptions(srcValue, dstValue, visited, opts, depth+1); err != nil { 398 | return err 399 | } 400 | 401 | dst.SetMapIndex(dstKey, dstValue) 402 | } 403 | return nil 404 | } 405 | 406 | // CopySlice creates a deep copy of a slice 407 | func CopySlice[T any](src []T) []T { 408 | if src == nil { 409 | return nil 410 | } 411 | dst := make([]T, len(src)) 412 | for i, v := range src { 413 | copied, _ := TryCopy(v) 414 | dst[i] = copied 415 | } 416 | return dst 417 | } 418 | 419 | // CopyMap creates a deep copy of a map 420 | func CopyMap[K comparable, V any](src map[K]V) map[K]V { 421 | if src == nil { 422 | return nil 423 | } 424 | dst := make(map[K]V, len(src)) 425 | for k, v := range src { 426 | copiedKey, _ := TryCopy(k) 427 | copiedValue, _ := TryCopy(v) 428 | dst[copiedKey] = copiedValue 429 | } 430 | return dst 431 | } 432 | 433 | // MustCopy is like TryCopy but panics on error 434 | func MustCopy[T any](src T) T { 435 | dst, err := TryCopy(src) 436 | if err != nil { 437 | panic(err) 438 | } 439 | return dst 440 | } 441 | 442 | // ShallowCopy creates a shallow copy (for performance when deep copy is not needed) 443 | func ShallowCopy[T any](src T) T { 444 | return src 445 | } 446 | 447 | // CopyInterface copies an interface{} value with type preservation 448 | func CopyInterface(src any) (any, error) { 449 | if src == nil { 450 | return nil, nil 451 | } 452 | 453 | return TryAnyCopy(src) 454 | } 455 | -------------------------------------------------------------------------------- /struct.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2014 Dmitry Ponomarev 2 | // 3 | // Permission is hereby granted, free of charge, to any person obtaining a copy of 4 | // this software and associated documentation files (the "Software"), to deal in 5 | // the Software without restriction, including without limitation the rights to 6 | // use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 7 | // the Software, and to permit persons to whom the Software is furnished to do so, 8 | // subject to the following conditions: 9 | // 10 | // The above copyright notice and this permission notice shall be included in all 11 | // copies or substantial portions of the Software. 12 | // 13 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 15 | // FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 16 | // COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 17 | // IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 18 | // CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 19 | 20 | package gocast 21 | 22 | import ( 23 | "context" 24 | "reflect" 25 | "strings" 26 | "time" 27 | ) 28 | 29 | // TryCopyStruct convert any input type into the target structure 30 | // 31 | //go:inline 32 | func TryCopyStruct(dst, src any, tags ...string) (err error) { 33 | return TryCopyStructContext(context.Background(), dst, src, tags...) 34 | } 35 | 36 | // TryCopyStructContext convert any input type into the target structure 37 | func TryCopyStructContext(ctx context.Context, dst, src any, tags ...string) (err error) { 38 | if dst == nil || src == nil { 39 | if dst == nil { 40 | return wrapError(ErrInvalidParams, "TryCopyStructContext `destenation` parameter is nil") 41 | } 42 | return wrapError(ErrInvalidParams, "TryCopyStructContext `source` parameter is nil") 43 | } 44 | 45 | if sintf, ok := dst.(CastSetter); ok { 46 | if sintf.CastSet(ctx, src) == nil { 47 | return nil 48 | } 49 | } 50 | 51 | // Set time value in case of time.Time type as destination target 52 | switch dst.(type) { 53 | case time.Time, *time.Time: 54 | return setFieldTimeValue(reflect.ValueOf(dst), src) 55 | } 56 | 57 | var ( 58 | destVal = reflectTarget(reflect.ValueOf(dst)) 59 | destType = destVal.Type() 60 | destFieldTypes = ReflectStructFields(destType) 61 | srcVal = reflectTarget(reflect.ValueOf(src)) 62 | names []string 63 | v any 64 | ) 65 | 66 | // Check source type is map or struct, otherwise return error unsupported type 67 | if srcVal.Kind() != reflect.Map && srcVal.Kind() != reflect.Struct { 68 | return wrapError(ErrUnsupportedSourceType, destType.Name()) 69 | } 70 | 71 | // Iterate over destination fields and set values from source 72 | for _, ft := range destFieldTypes { 73 | field := destVal.FieldByName(ft.Name) 74 | if !field.CanSet() { 75 | continue 76 | } 77 | 78 | // Get passable field names 79 | if names = fieldNames(ft, tags...); len(names) < 1 { 80 | continue 81 | } 82 | 83 | // Get value from map or struct 84 | if srcVal.Kind() == reflect.Map { 85 | v = reflectMapValueByStringKeys(srcVal, names) 86 | } else { 87 | v, _ = ReflectStructFieldValue(srcVal, names...) 88 | } 89 | 90 | // Set field value 91 | if v == nil { 92 | err = setFieldValueReflect(ctx, field, reflect.Zero(field.Type())) 93 | } else { 94 | switch field.Kind() { 95 | case reflect.Struct: 96 | err = TryCopyStructContext(ctx, field.Addr().Interface(), v, tags...) 97 | default: 98 | var ( 99 | vl any 100 | ok = false 101 | ) 102 | if setter, _ := field.Interface().(CastSetter); setter != nil { 103 | err = setter.CastSet(ctx, v) 104 | ok = true 105 | } else if field.CanAddr() { 106 | if setter, _ := field.Addr().Interface().(CastSetter); setter != nil { 107 | err = setter.CastSet(ctx, v) 108 | ok = true 109 | } 110 | } 111 | if !ok { 112 | if vl, err = TryToTypeContext(ctx, v, field.Type(), tags...); err == nil { 113 | val := reflect.ValueOf(vl) 114 | if val.Kind() == reflect.Ptr && val.Kind() != field.Kind() { 115 | val = val.Elem() 116 | } 117 | err = setFieldValueReflect(ctx, field, val) 118 | } 119 | } 120 | } 121 | } 122 | 123 | if err != nil { 124 | err = wrapError(err, ft.Name) 125 | break 126 | } 127 | } 128 | 129 | return err 130 | } 131 | 132 | // Struct convert any input type into the target structure 133 | // 134 | //go:inline 135 | func Struct[R any](src any, tags ...string) (R, error) { 136 | return StructContext[R](context.Background(), src, tags...) 137 | } 138 | 139 | // StructContext convert any input type into the target structure 140 | // 141 | //go:inline 142 | func StructContext[R any](ctx context.Context, src any, tags ...string) (R, error) { 143 | var res R 144 | err := TryCopyStructContext(ctx, &res, src, tags...) 145 | return res, err 146 | } 147 | 148 | // StructFieldNames returns the field names from the structure 149 | func StructFieldNames(st any, tag string) []string { 150 | s := reflectTarget(reflect.ValueOf(st)) 151 | return ReflectStructFieldNames(s.Type(), tag) 152 | } 153 | 154 | // ReflectStructFieldNames returns the field names from the structure 155 | func ReflectStructFieldNames(t reflect.Type, tag string) []string { 156 | if t.Kind() != reflect.Struct { 157 | return nil 158 | } 159 | fields := make([]string, 0, t.NumField()) 160 | for i := 0; i < t.NumField(); i++ { 161 | tfield := t.Field(i) 162 | if tfield.Anonymous && tfield.Type.Kind() == reflect.Struct { 163 | fields = append(fields, ReflectStructFieldNames(tfield.Type, tag)...) 164 | } else { 165 | fname, _ := fieldName(tfield, tag) 166 | if fname != "" && fname != "-" { 167 | fields = append(fields, fname) 168 | } 169 | } 170 | } 171 | return fields 172 | } 173 | 174 | // StructAllFieldNames returns the field names from the structure including names from tags 175 | func StructAllFieldNames(st any, tags ...string) map[string][]string { 176 | return StructAllFieldNamesContext(context.Background(), st, tags...) 177 | } 178 | 179 | // StructAllFieldNamesContext returns the field names from the structure including names from tags 180 | func StructAllFieldNamesContext(ctx context.Context, st any, tags ...string) map[string][]string { 181 | return ReflectStructAllFieldNamesContext(ctx, reflectTarget(reflect.ValueOf(st)).Type(), tags...) 182 | } 183 | 184 | // ReflectStructAllFieldNames returns the field names from the structure including names from tags 185 | func ReflectStructAllFieldNames(t reflect.Type, tags ...string) map[string][]string { 186 | return ReflectStructAllFieldNamesContext(context.Background(), t, tags...) 187 | } 188 | 189 | // ReflectStructAllFieldNamesContext returns the field names from the structure including names from tags 190 | func ReflectStructAllFieldNamesContext(ctx context.Context, t reflect.Type, tags ...string) map[string][]string { 191 | if t.Kind() != reflect.Struct { 192 | return nil 193 | } 194 | fields := make(map[string][]string, t.NumField()) 195 | for i := 0; i < t.NumField(); i++ { 196 | tfield := t.Field(i) 197 | if fnames := filedNameAndTags(tfield, tags...); len(fnames) > 0 { 198 | fields[tfield.Name] = fnames 199 | } 200 | } 201 | return fields 202 | } 203 | 204 | // StructFieldTags returns Map with key->tag matching 205 | func StructFieldTags(st any, tag string) map[string]string { 206 | fields := map[string]string{} 207 | keys, values := StructFieldTagsUnsorted(st, tag) 208 | 209 | for i, k := range keys { 210 | fields[k] = values[i] 211 | } 212 | return fields 213 | } 214 | 215 | // StructFieldTagsUnsorted returns field names and tag targets separately 216 | // 217 | //go:inline 218 | func StructFieldTagsUnsorted(st any, tag string) ([]string, []string) { 219 | return ReflectStructFieldTagsUnsorted(reflectTarget(reflect.ValueOf(st)).Type(), tag) 220 | } 221 | 222 | // ReflectStructFieldTagsUnsorted returns field names and tag targets separately 223 | func ReflectStructFieldTagsUnsorted(t reflect.Type, tag string) ([]string, []string) { 224 | if t.Kind() != reflect.Struct { 225 | return nil, nil 226 | } 227 | 228 | var ( 229 | keys = make([]string, 0, t.NumField()) 230 | tags = make([]string, 0, t.NumField()) 231 | ) 232 | 233 | for i := 0; i < t.NumField(); i++ { 234 | f := t.Field(i) 235 | if f.Anonymous && f.Type.Kind() == reflect.Struct { 236 | k, v := ReflectStructFieldTagsUnsorted(f.Type, tag) 237 | keys = append(keys, k...) 238 | tags = append(tags, v...) 239 | } else { 240 | tag := strings.TrimSuffix(fieldTag(f, tag), ",omitempty") 241 | if len(tag) > 0 && tag != "-" { 242 | keys = append(keys, f.Name) 243 | tags = append(tags, tag) 244 | } 245 | } 246 | } 247 | return keys, tags 248 | } 249 | 250 | // ReflectStructFields returns the field names from the structure 251 | func ReflectStructFields(t reflect.Type) []reflect.StructField { 252 | if t.Kind() != reflect.Struct { 253 | return nil 254 | } 255 | fields := make([]reflect.StructField, 0, t.NumField()) 256 | for i := 0; i < t.NumField(); i++ { 257 | tfield := t.Field(i) 258 | if tfield.Anonymous && tfield.Type.Kind() == reflect.Struct { 259 | fields = append(fields, ReflectStructFields(tfield.Type)...) 260 | } else { 261 | fields = append(fields, tfield) 262 | } 263 | } 264 | return fields 265 | } 266 | 267 | // StructFieldValue returns the value of the struct field 268 | // 269 | //go:inline 270 | func StructFieldValue(st any, names ...string) (any, error) { 271 | structVal := reflectTarget(reflect.ValueOf(st)) 272 | return ReflectStructFieldValue(structVal, names...) 273 | } 274 | 275 | // ReflectStructFieldValue returns the value of the struct field 276 | func ReflectStructFieldValue(st reflect.Value, names ...string) (any, error) { 277 | structVal := reflectTarget(st) 278 | structType := structVal.Type() 279 | for _, name := range names { 280 | if _, ok := structType.FieldByName(name); ok { 281 | return structVal.FieldByName(name).Interface(), nil 282 | } 283 | } 284 | return nil, wrapError(ErrStructFieldNameUndefined, strings.Join(names, ", ")) 285 | } 286 | 287 | // SetStructFieldValue puts value into the struct field 288 | func SetStructFieldValue(ctx context.Context, st any, name string, value any) (err error) { 289 | s := reflectTarget(reflect.ValueOf(st)) 290 | t := s.Type() 291 | if _, ok := t.FieldByName(name); ok { 292 | field := s.FieldByName(name) 293 | if !field.CanSet() { 294 | return wrapError(ErrStructFieldValueCantBeChanged, name) 295 | } 296 | err = setFieldValue(ctx, field, value) 297 | return err 298 | } 299 | return wrapError(ErrStructFieldNameUndefined, name) 300 | } 301 | 302 | // IsStruct returns true if the value is a struct 303 | // 304 | //go:inline 305 | func IsStruct(v any) bool { 306 | return v != nil && reflect.TypeOf(v).Kind() == reflect.Struct 307 | } 308 | 309 | /////////////////////////////////////////////////////////////////////////////// 310 | /// MARK: Helpers 311 | /////////////////////////////////////////////////////////////////////////////// 312 | 313 | var fieldNameArr = []string{"field", "schema", "sql", "json", "xml", "yaml"} 314 | 315 | func setFieldValue(ctx context.Context, field reflect.Value, value any) (err error) { 316 | switch field.Interface().(type) { 317 | case time.Time, *time.Time: 318 | err = setFieldTimeValue(field, value) 319 | default: 320 | if setter, _ := field.Interface().(CastSetter); setter != nil { 321 | return setter.CastSet(ctx, value) 322 | } else if field.CanAddr() { 323 | if setter, _ := field.Addr().Interface().(CastSetter); setter != nil { 324 | return setter.CastSet(ctx, value) 325 | } else if vl := reflect.ValueOf(value); field.Kind() == vl.Kind() || field.Kind() == reflect.Interface { 326 | field.Set(vl) 327 | } else { 328 | return wrapError(ErrUnsupportedType, field.Type().String()) 329 | } 330 | } else if vl := reflect.ValueOf(value); field.Kind() == vl.Kind() || field.Kind() == reflect.Interface { 331 | field.Set(vl) 332 | } else { 333 | return wrapError(ErrUnsupportedType, field.Type().String()) 334 | } 335 | } 336 | return err 337 | } 338 | 339 | func setFieldValueNoCastSetter(ctx context.Context, field reflect.Value, value any, autoCast ...bool) (err error) { 340 | switch field.Interface().(type) { 341 | case time.Time, *time.Time: 342 | err = setFieldTimeValue(field, value) 343 | default: 344 | vl := reflect.ValueOf(value) 345 | if field.Kind() == vl.Kind() || field.Kind() == reflect.Interface { 346 | field.Set(vl) 347 | } else if len(autoCast) > 0 && autoCast[0] { 348 | if nval, err := ReflectTryToTypeContext(ctx, vl, field.Type(), true); err == nil { 349 | val := reflect.ValueOf(nval) 350 | if val.Kind() == reflect.Ptr && val.Kind() != field.Kind() { 351 | val = val.Elem() 352 | } 353 | field.Set(val) 354 | } else { 355 | return wrapError(ErrUnsupportedType, field.Type().String()) 356 | } 357 | } else { 358 | return wrapError(ErrUnsupportedType, field.Type().String()) 359 | } 360 | } 361 | return err 362 | } 363 | 364 | func setFieldValueReflect(ctx context.Context, field, value reflect.Value) (err error) { 365 | switch field.Interface().(type) { 366 | case time.Time, *time.Time: 367 | err = setFieldTimeValue(field, value.Interface()) 368 | default: 369 | if setter, _ := field.Interface().(CastSetter); setter != nil { 370 | return setter.CastSet(ctx, value.Interface()) 371 | } else if field.CanAddr() { 372 | if setter, _ := field.Addr().Interface().(CastSetter); setter != nil { 373 | return setter.CastSet(ctx, value.Interface()) 374 | } else if field.Kind() == value.Kind() || field.Kind() == reflect.Interface { 375 | field.Set(value) 376 | } else { 377 | return wrapError(ErrUnsupportedType, field.Type().String()) 378 | } 379 | } else if field.Kind() == value.Kind() || field.Kind() == reflect.Interface { 380 | field.Set(value) 381 | } else { 382 | return wrapError(ErrUnsupportedType, field.Type().String()) 383 | } 384 | } 385 | return err 386 | } 387 | 388 | func setFieldTimeValue(field reflect.Value, value any) (err error) { 389 | switch v := value.(type) { 390 | case nil: 391 | s := reflectTarget(field) 392 | s.Set(reflect.ValueOf(time.Time{})) 393 | case time.Time: 394 | s := reflectTarget(field) 395 | s.Set(reflect.ValueOf(v)) 396 | case *time.Time: 397 | s := reflectTarget(field) 398 | s.Set(reflect.ValueOf(*v)) 399 | case string: 400 | var tm time.Time 401 | if tm, err = ParseTime(v); err == nil { 402 | s := reflectTarget(field) 403 | s.Set(reflect.ValueOf(tm)) 404 | } 405 | case int64: 406 | s := reflectTarget(field) 407 | s.Set(reflect.ValueOf(time.Unix(v, 0))) 408 | case uint64: 409 | s := reflectTarget(field) 410 | s.Set(reflect.ValueOf(time.Unix(int64(v), 0))) 411 | default: 412 | err = wrapError(ErrUnsupportedType, field.String()) 413 | } 414 | return err 415 | } 416 | 417 | func fieldNames(f reflect.StructField, tags ...string) []string { 418 | if len(tags) > 0 { 419 | names := fieldTagArr(f, tags[0]) 420 | switch names[0] { 421 | case "", "-": 422 | return []string{f.Name} 423 | default: 424 | } 425 | return []string{names[0], f.Name} 426 | } 427 | return []string{f.Name, f.Name} 428 | } 429 | 430 | func fieldNameFromTags(f reflect.StructField, tags ...string) (name string, omitempty bool) { 431 | if len(tags) == 0 { 432 | return f.Name, false 433 | } 434 | return fieldName(f, tags[0]) 435 | } 436 | 437 | func fieldName(f reflect.StructField, tag string) (name string, omitempty bool) { 438 | names := fieldTagArr(f, tag) 439 | name = names[0] 440 | if len(names) > 1 && names[len(names)-1] == "omitempty" { 441 | omitempty = true 442 | } 443 | if name == "" { 444 | name = f.Name 445 | } 446 | return name, omitempty 447 | } 448 | 449 | func fieldTagArr(f reflect.StructField, tag string) []string { 450 | return strings.Split(fieldTag(f, tag), ",") 451 | } 452 | 453 | func fieldTag(f reflect.StructField, tag string) string { 454 | if tag == "-" { 455 | return f.Name 456 | } 457 | var ( 458 | fields string 459 | tags []string 460 | ) 461 | if tag != "" { 462 | tags = strings.Split(tag, ",") 463 | } else { 464 | tags = fieldNameArr 465 | } 466 | for _, k := range tags { 467 | fields = f.Tag.Get(k) 468 | if fields != "" { 469 | break 470 | } 471 | } 472 | if fields != "" { 473 | if fields == "-" { 474 | return "" 475 | } 476 | return fields 477 | } 478 | return f.Name 479 | } 480 | 481 | func filedNameAndTags(f reflect.StructField, tags ...string) []string { 482 | names := []string{f.Name} 483 | if len(tags) == 0 { 484 | return names 485 | } 486 | deDup := map[string]bool{f.Name: true} 487 | for _, tag := range tags { 488 | if tag == "-" || tag == "" { 489 | continue 490 | } 491 | if tagName := strings.TrimSuffix(f.Tag.Get(tag), ",omitempty"); tagName != "" && tagName != "-" && !deDup[tagName] { 492 | deDup[tagName] = true 493 | names = append(names, tagName) 494 | } 495 | } 496 | return names 497 | } 498 | -------------------------------------------------------------------------------- /bool_test.go: -------------------------------------------------------------------------------- 1 | package gocast 2 | 3 | import ( 4 | "reflect" 5 | "testing" 6 | 7 | "github.com/stretchr/testify/assert" 8 | ) 9 | 10 | func TestBool(t *testing.T) { 11 | tests := []struct { 12 | name string 13 | src any 14 | expected bool 15 | }{ 16 | // nil 17 | {"nil", nil, false}, 18 | 19 | // string cases 20 | {"string_1", "1", true}, 21 | {"string_T", "T", true}, 22 | {"string_t", "t", true}, 23 | {"string_true", "true", true}, 24 | {"string_TRUE", "TRUE", true}, 25 | {"string_True", "True", true}, 26 | {"string_empty", "", false}, 27 | {"string_false", "false", false}, 28 | {"string_0", "0", false}, 29 | {"string_f", "f", false}, 30 | {"string_F", "F", false}, 31 | {"string_other", "other", false}, 32 | 33 | // []byte cases 34 | {"bytes_1", []byte("1"), true}, 35 | {"bytes_T", []byte("T"), true}, 36 | {"bytes_t", []byte("t"), true}, 37 | {"bytes_true", []byte("true"), true}, 38 | {"bytes_TRUE", []byte("TRUE"), true}, 39 | {"bytes_True", []byte("True"), true}, 40 | {"bytes_empty", []byte(""), false}, 41 | {"bytes_false", []byte("false"), false}, 42 | {"bytes_0", []byte("0"), false}, 43 | {"bytes_f", []byte("f"), false}, 44 | {"bytes_F", []byte("F"), false}, 45 | {"bytes_other", []byte("other"), false}, 46 | 47 | // bool cases 48 | {"bool_true", true, true}, 49 | {"bool_false", false, false}, 50 | 51 | // int cases 52 | {"int_positive", 120, true}, 53 | {"int_negative", -120, true}, 54 | {"int_zero", 0, false}, 55 | 56 | // int8 cases 57 | {"int8_positive", int8(120), true}, 58 | {"int8_negative", int8(-120), true}, 59 | {"int8_zero", int8(0), false}, 60 | 61 | // int16 cases 62 | {"int16_positive", int16(120), true}, 63 | {"int16_negative", int16(-120), true}, 64 | {"int16_zero", int16(0), false}, 65 | 66 | // int32 cases 67 | {"int32_positive", int32(120), true}, 68 | {"int32_negative", int32(-120), true}, 69 | {"int32_zero", int32(0), false}, 70 | 71 | // int64 cases 72 | {"int64_positive", int64(120), true}, 73 | {"int64_negative", int64(-120), true}, 74 | {"int64_zero", int64(0), false}, 75 | 76 | // uint cases 77 | {"uint_positive", uint(120), true}, 78 | {"uint_zero", uint(0), false}, 79 | 80 | // uint8 cases 81 | {"uint8_positive", uint8(120), true}, 82 | {"uint8_zero", uint8(0), false}, 83 | 84 | // uint16 cases 85 | {"uint16_positive", uint16(120), true}, 86 | {"uint16_zero", uint16(0), false}, 87 | 88 | // uint32 cases 89 | {"uint32_positive", uint32(120), true}, 90 | {"uint32_zero", uint32(0), false}, 91 | 92 | // uint64 cases 93 | {"uint64_positive", uint64(120), true}, 94 | {"uint64_zero", uint64(0), false}, 95 | 96 | // uintptr cases 97 | {"uintptr_positive", uintptr(120), true}, 98 | {"uintptr_zero", uintptr(0), false}, 99 | 100 | // float32 cases 101 | {"float32_positive", float32(1.5), true}, 102 | {"float32_negative", float32(-1.5), true}, 103 | {"float32_zero", float32(0.0), false}, 104 | 105 | // float64 cases 106 | {"float64_positive", float64(1.5), true}, 107 | {"float64_negative", float64(-1.5), true}, 108 | {"float64_zero", float64(0.0), false}, 109 | 110 | // slice cases 111 | {"slice_int_nonempty", []int{1, 2, 3}, true}, 112 | {"slice_int_empty", []int{}, false}, 113 | {"slice_int8_nonempty", []int8{1, 2}, true}, 114 | {"slice_int8_empty", []int8{}, false}, 115 | {"slice_int16_nonempty", []int16{1, 2}, true}, 116 | {"slice_int16_empty", []int16{}, false}, 117 | {"slice_int32_nonempty", []int32{1, 2}, true}, 118 | {"slice_int32_empty", []int32{}, false}, 119 | {"slice_int64_nonempty", []int64{1, 2}, true}, 120 | {"slice_int64_empty", []int64{}, false}, 121 | {"slice_uint_nonempty", []uint{1, 2}, true}, 122 | {"slice_uint_empty", []uint{}, false}, 123 | {"slice_uint16_nonempty", []uint16{1, 2}, true}, 124 | {"slice_uint16_empty", []uint16{}, false}, 125 | {"slice_uint32_nonempty", []uint32{1, 2}, true}, 126 | {"slice_uint32_empty", []uint32{}, false}, 127 | {"slice_uint64_nonempty", []uint64{1, 2}, true}, 128 | {"slice_uint64_empty", []uint64{}, false}, 129 | {"slice_float32_nonempty", []float32{1.0, 2.0}, true}, 130 | {"slice_float32_empty", []float32{}, false}, 131 | {"slice_float64_nonempty", []float64{1.0, 2.0}, true}, 132 | {"slice_float64_empty", []float64{}, false}, 133 | {"slice_bool_nonempty", []bool{true, false}, true}, 134 | {"slice_bool_empty", []bool{}, false}, 135 | {"slice_any_nonempty", []any{1, "test"}, true}, 136 | {"slice_any_empty", []any{}, false}, 137 | } 138 | 139 | for _, test := range tests { 140 | t.Run(test.name, func(t *testing.T) { 141 | result := Bool(test.src) 142 | assert.Equal(t, test.expected, result, "Bool(%v) should return %v", test.src, test.expected) 143 | }) 144 | } 145 | } 146 | 147 | func TestReflectToBool(t *testing.T) { 148 | tests := []struct { 149 | name string 150 | src any 151 | expected bool 152 | }{ 153 | // Invalid reflect.Value 154 | {"invalid", nil, false}, // будет создан невалидный reflect.Value 155 | 156 | // string cases 157 | {"string_1", "1", true}, 158 | {"string_true", "true", true}, 159 | {"string_t", "t", true}, 160 | {"string_empty", "", false}, 161 | {"string_false", "false", false}, 162 | {"string_other", "other", false}, 163 | 164 | // []byte cases (через slice reflection) 165 | {"bytes_1", []byte("1"), true}, 166 | {"bytes_true", []byte("true"), true}, 167 | {"bytes_t", []byte("t"), true}, 168 | {"bytes_empty", []byte(""), false}, 169 | {"bytes_false", []byte("false"), false}, 170 | 171 | // slice cases 172 | {"slice_nonempty", []int{1, 2, 3}, true}, 173 | {"slice_empty", []int{}, false}, 174 | {"slice_string_nonempty", []string{"a", "b"}, true}, 175 | {"slice_string_empty", []string{}, false}, 176 | 177 | // array cases 178 | {"array_nonempty", [3]int{1, 2, 3}, true}, 179 | {"array_empty", [0]int{}, false}, 180 | 181 | // map cases 182 | {"map_nonempty", map[string]int{"a": 1}, true}, 183 | {"map_empty", map[string]int{}, false}, 184 | 185 | // bool cases 186 | {"bool_true", true, true}, 187 | {"bool_false", false, false}, 188 | 189 | // int cases 190 | {"int_positive", 120, true}, 191 | {"int_negative", -120, true}, 192 | {"int_zero", 0, false}, 193 | 194 | // int8 cases 195 | {"int8_positive", int8(120), true}, 196 | {"int8_zero", int8(0), false}, 197 | 198 | // int16 cases 199 | {"int16_positive", int16(120), true}, 200 | {"int16_zero", int16(0), false}, 201 | 202 | // int32 cases 203 | {"int32_positive", int32(120), true}, 204 | {"int32_zero", int32(0), false}, 205 | 206 | // int64 cases 207 | {"int64_positive", int64(120), true}, 208 | {"int64_zero", int64(0), false}, 209 | 210 | // uint cases 211 | {"uint_positive", uint(120), true}, 212 | {"uint_zero", uint(0), false}, 213 | 214 | // uint8 cases 215 | {"uint8_positive", uint8(120), true}, 216 | {"uint8_zero", uint8(0), false}, 217 | 218 | // uint16 cases 219 | {"uint16_positive", uint16(120), true}, 220 | {"uint16_zero", uint16(0), false}, 221 | 222 | // uint32 cases 223 | {"uint32_positive", uint32(120), true}, 224 | {"uint32_zero", uint32(0), false}, 225 | 226 | // uint64 cases 227 | {"uint64_positive", uint64(120), true}, 228 | {"uint64_zero", uint64(0), false}, 229 | 230 | // uintptr cases 231 | {"uintptr_positive", uintptr(120), true}, 232 | {"uintptr_zero", uintptr(0), false}, 233 | 234 | // float32 cases 235 | {"float32_positive", float32(1.5), true}, 236 | {"float32_zero", float32(0.0), false}, 237 | 238 | // float64 cases 239 | {"float64_positive", float64(1.5), true}, 240 | {"float64_zero", float64(0.0), false}, 241 | 242 | // other types (should return false) 243 | {"complex64", complex64(1 + 2i), false}, 244 | {"complex128", complex128(1 + 2i), false}, 245 | {"chan", make(chan int), false}, 246 | {"func", func() {}, false}, 247 | {"ptr", new(int), false}, 248 | {"struct", struct{ X int }{X: 1}, false}, 249 | {"interface", interface{}(nil), false}, 250 | } 251 | 252 | for _, test := range tests { 253 | t.Run(test.name, func(t *testing.T) { 254 | var v reflect.Value 255 | if test.name == "invalid" { 256 | v = reflect.Value{} // создаем невалидный reflect.Value 257 | } else { 258 | v = reflect.ValueOf(test.src) 259 | } 260 | 261 | result := ReflectToBool(v) 262 | assert.Equal(t, test.expected, result, "ReflectToBool(%v) should return %v", test.src, test.expected) 263 | }) 264 | } 265 | } 266 | 267 | func TestReflectToBoolSpecialCases(t *testing.T) { 268 | // Тест для []byte через reflection (особый случай в коде) 269 | t.Run("bytes_reflection_special", func(t *testing.T) { 270 | tests := []struct { 271 | name string 272 | bytes []byte 273 | expected bool 274 | }{ 275 | {"bytes_1", []byte("1"), true}, 276 | {"bytes_true", []byte("true"), true}, 277 | {"bytes_t", []byte("t"), true}, 278 | {"bytes_empty", []byte(""), false}, 279 | {"bytes_false", []byte("false"), false}, 280 | {"bytes_other", []byte("other"), false}, 281 | } 282 | 283 | for _, test := range tests { 284 | t.Run(test.name, func(t *testing.T) { 285 | v := reflect.ValueOf(test.bytes) 286 | result := ReflectToBool(v) 287 | assert.Equal(t, test.expected, result) 288 | }) 289 | } 290 | }) 291 | 292 | // Тест для slice не-bytes типов 293 | t.Run("non_bytes_slices", func(t *testing.T) { 294 | tests := []struct { 295 | name string 296 | slice any 297 | expected bool 298 | }{ 299 | {"slice_string_nonempty", []string{"a", "b"}, true}, 300 | {"slice_string_empty", []string{}, false}, 301 | {"slice_interface_nonempty", []interface{}{1, "a"}, true}, 302 | {"slice_interface_empty", []interface{}{}, false}, 303 | } 304 | 305 | for _, test := range tests { 306 | t.Run(test.name, func(t *testing.T) { 307 | v := reflect.ValueOf(test.slice) 308 | result := ReflectToBool(v) 309 | assert.Equal(t, test.expected, result) 310 | }) 311 | } 312 | }) 313 | } 314 | 315 | func TestBoolEdgeCases(t *testing.T) { 316 | // Тестируем граничные случаи 317 | 318 | t.Run("complex_types_fallback_to_reflection", func(t *testing.T) { 319 | // Типы, которые не обрабатываются напрямую в Bool и попадают в ReflectToBool 320 | tests := []struct { 321 | name string 322 | src any 323 | expected bool 324 | }{ 325 | {"struct", struct{ X int }{X: 1}, false}, 326 | {"ptr_non_nil", new(int), false}, 327 | {"chan", make(chan int), false}, 328 | {"func", func() {}, false}, 329 | {"complex64", complex64(1 + 2i), false}, 330 | {"complex128", complex128(1 + 2i), false}, 331 | {"map_string_int_nonempty", map[string]int{"a": 1}, true}, 332 | {"map_string_int_empty", map[string]int{}, false}, 333 | {"array", [3]int{1, 2, 3}, true}, 334 | {"array_empty", [0]int{}, false}, 335 | } 336 | 337 | for _, test := range tests { 338 | t.Run(test.name, func(t *testing.T) { 339 | result := Bool(test.src) 340 | assert.Equal(t, test.expected, result) 341 | }) 342 | } 343 | }) 344 | 345 | t.Run("string_case_sensitivity", func(t *testing.T) { 346 | // Проверяем case-insensitive сравнение для "true" 347 | cases := []struct { 348 | str string 349 | expected bool 350 | }{ 351 | {"true", true}, 352 | {"TRUE", true}, 353 | {"True", true}, 354 | {"TrUe", true}, 355 | {"tRuE", true}, 356 | {"false", false}, 357 | {"FALSE", false}, 358 | {"False", false}, 359 | } 360 | 361 | for _, test := range cases { 362 | t.Run("string_"+test.str, func(t *testing.T) { 363 | result := Bool(test.str) 364 | assert.Equal(t, test.expected, result) 365 | }) 366 | } 367 | }) 368 | 369 | t.Run("bytes_case_sensitivity", func(t *testing.T) { 370 | // Проверяем case-insensitive сравнение для []byte("true") 371 | cases := []struct { 372 | bytes []byte 373 | expected bool 374 | }{ 375 | {[]byte("true"), true}, 376 | {[]byte("TRUE"), true}, 377 | {[]byte("True"), true}, 378 | {[]byte("TrUe"), true}, 379 | {[]byte("tRuE"), true}, 380 | {[]byte("false"), false}, 381 | {[]byte("FALSE"), false}, 382 | {[]byte("False"), false}, 383 | } 384 | 385 | for _, test := range cases { 386 | t.Run("bytes_"+string(test.bytes), func(t *testing.T) { 387 | result := Bool(test.bytes) 388 | assert.Equal(t, test.expected, result) 389 | }) 390 | } 391 | }) 392 | } 393 | 394 | func BenchmarkBool(b *testing.B) { 395 | values := []any{120, uint64(122), "f", "true", "", []byte("t"), true, false, 0.} 396 | for n := 0; n < b.N; n++ { 397 | _ = Bool(values[n%len(values)]) 398 | } 399 | } 400 | 401 | func BenchmarkToBoolByReflect(b *testing.B) { 402 | var ( 403 | baseValues = []any{120, uint64(122), "f", "true", "", []byte("t"), true, false, 0.} 404 | values = []reflect.Value{} 405 | ) 406 | for _, v := range baseValues { 407 | values = append(values, reflect.ValueOf(v)) 408 | } 409 | for n := 0; n < b.N; n++ { 410 | _ = ReflectToBool(values[n%len(values)]) 411 | } 412 | } 413 | 414 | func BenchmarkBoolTypes(b *testing.B) { 415 | b.Run("string", func(b *testing.B) { 416 | for i := 0; i < b.N; i++ { 417 | _ = Bool("true") 418 | } 419 | }) 420 | 421 | b.Run("int", func(b *testing.B) { 422 | for i := 0; i < b.N; i++ { 423 | _ = Bool(42) 424 | } 425 | }) 426 | 427 | b.Run("bool", func(b *testing.B) { 428 | for i := 0; i < b.N; i++ { 429 | _ = Bool(true) 430 | } 431 | }) 432 | 433 | b.Run("bytes", func(b *testing.B) { 434 | bytes := []byte("true") 435 | for i := 0; i < b.N; i++ { 436 | _ = Bool(bytes) 437 | } 438 | }) 439 | 440 | b.Run("slice", func(b *testing.B) { 441 | slice := []int{1, 2, 3} 442 | for i := 0; i < b.N; i++ { 443 | _ = Bool(slice) 444 | } 445 | }) 446 | 447 | b.Run("reflection_fallback", func(b *testing.B) { 448 | m := map[string]int{"a": 1} 449 | for i := 0; i < b.N; i++ { 450 | _ = Bool(m) 451 | } 452 | }) 453 | } 454 | 455 | // Дополнительные тесты для 100% покрытия 456 | func TestBoolAdditionalCoverage(t *testing.T) { 457 | t.Run("missing_slice_uint8", func(t *testing.T) { 458 | // В Go []uint8 и []byte это одно и то же, поэтому []uint8{1, 2} будет обрабатываться 459 | // как []byte в специальном случае ReflectToBool и возвращать false, 460 | // поскольку {1, 2} не равно "1", "true" или "t" 461 | tests := []struct { 462 | name string 463 | src any 464 | expected bool 465 | }{ 466 | {"slice_uint8_nonempty", []uint8{1, 2}, false}, // Не равно "1", "true", "t" 467 | {"slice_uint8_empty", []uint8{}, false}, // Пустой 468 | {"slice_uint8_true", []uint8("true"), true}, // Равно "true" 469 | {"slice_uint8_1", []uint8("1"), true}, // Равно "1" 470 | {"slice_uint8_t", []uint8("t"), true}, // Равно "t" 471 | } 472 | 473 | for _, test := range tests { 474 | t.Run(test.name, func(t *testing.T) { 475 | result := Bool(test.src) 476 | assert.Equal(t, test.expected, result) 477 | }) 478 | } 479 | }) 480 | 481 | t.Run("special_reflect_cases", func(t *testing.T) { 482 | // Тестируем особые случаи для ReflectToBool 483 | tests := []struct { 484 | name string 485 | create func() reflect.Value 486 | expected bool 487 | }{ 488 | { 489 | "invalid_value", 490 | func() reflect.Value { return reflect.Value{} }, 491 | false, 492 | }, 493 | { 494 | "nil_pointer", 495 | func() reflect.Value { 496 | var p *int 497 | return reflect.ValueOf(p) 498 | }, 499 | false, 500 | }, 501 | { 502 | "interface_with_nil", 503 | func() reflect.Value { 504 | var i interface{} = nil 505 | return reflect.ValueOf(i) 506 | }, 507 | false, 508 | }, 509 | } 510 | 511 | for _, test := range tests { 512 | t.Run(test.name, func(t *testing.T) { 513 | v := test.create() 514 | result := ReflectToBool(v) 515 | assert.Equal(t, test.expected, result) 516 | }) 517 | } 518 | }) 519 | 520 | t.Run("bytes_edge_cases", func(t *testing.T) { 521 | // Дополнительные тесты для []byte случаев 522 | tests := []struct { 523 | name string 524 | bytes []byte 525 | expected bool 526 | }{ 527 | {"bytes_nil", nil, false}, // nil []byte 528 | {"bytes_single_space", []byte(" "), false}, // непустой, но не равен истинным значениям 529 | {"bytes_mixed_case", []byte("TrUe"), true}, // mixed case - должно быть true через bytes.EqualFold 530 | {"bytes_case_sensitive_t", []byte("T"), true}, // заглавная T 531 | } 532 | 533 | for _, test := range tests { 534 | t.Run(test.name, func(t *testing.T) { 535 | result := Bool(test.bytes) 536 | assert.Equal(t, test.expected, result) 537 | }) 538 | } 539 | }) 540 | 541 | t.Run("reflect_string_edge_cases", func(t *testing.T) { 542 | // Тесты для строковых случаев через рефлексию 543 | tests := []struct { 544 | name string 545 | str string 546 | expected bool 547 | }{ 548 | {"reflect_string_T", "T", false}, // В ReflectToBool нет "T", только "t" 549 | {"reflect_string_1", "1", true}, 550 | {"reflect_string_true", "true", true}, 551 | {"reflect_string_t", "t", true}, 552 | {"reflect_string_false", "false", false}, 553 | {"reflect_string_empty", "", false}, 554 | } 555 | 556 | for _, test := range tests { 557 | t.Run(test.name, func(t *testing.T) { 558 | v := reflect.ValueOf(test.str) 559 | result := ReflectToBool(v) 560 | assert.Equal(t, test.expected, result) 561 | }) 562 | } 563 | }) 564 | 565 | t.Run("special_numeric_cases", func(t *testing.T) { 566 | // Тесты для особых числовых случаев 567 | tests := []struct { 568 | name string 569 | src any 570 | expected bool 571 | }{ 572 | {"float32_zero", float32(0.0), false}, 573 | {"float64_zero", float64(0.0), false}, 574 | {"float32_tiny_positive", float32(1e-38), true}, 575 | {"float64_tiny_positive", float64(1e-308), true}, 576 | {"int_min", int(-2147483648), true}, 577 | {"int_max", int(2147483647), true}, 578 | } 579 | 580 | for _, test := range tests { 581 | t.Run(test.name, func(t *testing.T) { 582 | result := Bool(test.src) 583 | assert.Equal(t, test.expected, result) 584 | }) 585 | } 586 | }) 587 | } 588 | --------------------------------------------------------------------------------