├── .gitignore ├── LICENSE.txt ├── README.md ├── go.mod ├── go.sum ├── makefile └── reflector ├── performance_test.go ├── reflector.go ├── reflector_test.go ├── tmp └── testmodels.go ├── utils.go └── walk.go /.gitignore: -------------------------------------------------------------------------------- 1 | *.sw? 2 | tags 3 | .DS_Store 4 | /.idea 5 | 6 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | 2 | Apache License 3 | Version 2.0, January 2004 4 | http://www.apache.org/licenses/ 5 | 6 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 7 | 8 | 1. Definitions. 9 | 10 | "License" shall mean the terms and conditions for use, reproduction, 11 | and distribution as defined by Sections 1 through 9 of this document. 12 | 13 | "Licensor" shall mean the copyright owner or entity authorized by 14 | the copyright owner that is granting the License. 15 | 16 | "Legal Entity" shall mean the union of the acting entity and all 17 | other entities that control, are controlled by, or are under common 18 | control with that entity. For the purposes of this definition, 19 | "control" means (i) the power, direct or indirect, to cause the 20 | direction or management of such entity, whether by contract or 21 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 22 | outstanding shares, or (iii) beneficial ownership of such entity. 23 | 24 | "You" (or "Your") shall mean an individual or Legal Entity 25 | exercising permissions granted by this License. 26 | 27 | "Source" form shall mean the preferred form for making modifications, 28 | including but not limited to software source code, documentation 29 | source, and configuration files. 30 | 31 | "Object" form shall mean any form resulting from mechanical 32 | transformation or translation of a Source form, including but 33 | not limited to compiled object code, generated documentation, 34 | and conversions to other media types. 35 | 36 | "Work" shall mean the work of authorship, whether in Source or 37 | Object form, made available under the License, as indicated by a 38 | copyright notice that is included in or attached to the work 39 | (an example is provided in the Appendix below). 40 | 41 | "Derivative Works" shall mean any work, whether in Source or Object 42 | form, that is based on (or derived from) the Work and for which the 43 | editorial revisions, annotations, elaborations, or other modifications 44 | represent, as a whole, an original work of authorship. For the purposes 45 | of this License, Derivative Works shall not include works that remain 46 | separable from, or merely link (or bind by name) to the interfaces of, 47 | the Work and Derivative Works thereof. 48 | 49 | "Contribution" shall mean any work of authorship, including 50 | the original version of the Work and any modifications or additions 51 | to that Work or Derivative Works thereof, that is intentionally 52 | submitted to Licensor for inclusion in the Work by the copyright owner 53 | or by an individual or Legal Entity authorized to submit on behalf of 54 | the copyright owner. For the purposes of this definition, "submitted" 55 | means any form of electronic, verbal, or written communication sent 56 | to the Licensor or its representatives, including but not limited to 57 | communication on electronic mailing lists, source code control systems, 58 | and issue tracking systems that are managed by, or on behalf of, the 59 | Licensor for the purpose of discussing and improving the Work, but 60 | excluding communication that is conspicuously marked or otherwise 61 | designated in writing by the copyright owner as "Not a Contribution." 62 | 63 | "Contributor" shall mean Licensor and any individual or Legal Entity 64 | on behalf of whom a Contribution has been received by Licensor and 65 | subsequently incorporated within the Work. 66 | 67 | 2. Grant of Copyright License. Subject to the terms and conditions of 68 | this License, each Contributor hereby grants to You a perpetual, 69 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 70 | copyright license to reproduce, prepare Derivative Works of, 71 | publicly display, publicly perform, sublicense, and distribute the 72 | Work and such Derivative Works in Source or Object form. 73 | 74 | 3. Grant of Patent License. Subject to the terms and conditions of 75 | this License, each Contributor hereby grants to You a perpetual, 76 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 77 | (except as stated in this section) patent license to make, have made, 78 | use, offer to sell, sell, import, and otherwise transfer the Work, 79 | where such license applies only to those patent claims licensable 80 | by such Contributor that are necessarily infringed by their 81 | Contribution(s) alone or by combination of their Contribution(s) 82 | with the Work to which such Contribution(s) was submitted. If You 83 | institute patent litigation against any entity (including a 84 | cross-claim or counterclaim in a lawsuit) alleging that the Work 85 | or a Contribution incorporated within the Work constitutes direct 86 | or contributory patent infringement, then any patent licenses 87 | granted to You under this License for that Work shall terminate 88 | as of the date such litigation is filed. 89 | 90 | 4. Redistribution. You may reproduce and distribute copies of the 91 | Work or Derivative Works thereof in any medium, with or without 92 | modifications, and in Source or Object form, provided that You 93 | meet the following conditions: 94 | 95 | (a) You must give any other recipients of the Work or 96 | Derivative Works a copy of this License; and 97 | 98 | (b) You must cause any modified files to carry prominent notices 99 | stating that You changed the files; and 100 | 101 | (c) You must retain, in the Source form of any Derivative Works 102 | that You distribute, all copyright, patent, trademark, and 103 | attribution notices from the Source form of the Work, 104 | excluding those notices that do not pertain to any part of 105 | the Derivative Works; and 106 | 107 | (d) If the Work includes a "NOTICE" text file as part of its 108 | distribution, then any Derivative Works that You distribute must 109 | include a readable copy of the attribution notices contained 110 | within such NOTICE file, excluding those notices that do not 111 | pertain to any part of the Derivative Works, in at least one 112 | of the following places: within a NOTICE text file distributed 113 | as part of the Derivative Works; within the Source form or 114 | documentation, if provided along with the Derivative Works; or, 115 | within a display generated by the Derivative Works, if and 116 | wherever such third-party notices normally appear. The contents 117 | of the NOTICE file are for informational purposes only and 118 | do not modify the License. You may add Your own attribution 119 | notices within Derivative Works that You distribute, alongside 120 | or as an addendum to the NOTICE text from the Work, provided 121 | that such additional attribution notices cannot be construed 122 | as modifying the License. 123 | 124 | You may add Your own copyright statement to Your modifications and 125 | may provide additional or different license terms and conditions 126 | for use, reproduction, or distribution of Your modifications, or 127 | for any such Derivative Works as a whole, provided Your use, 128 | reproduction, and distribution of the Work otherwise complies with 129 | the conditions stated in this License. 130 | 131 | 5. Submission of Contributions. Unless You explicitly state otherwise, 132 | any Contribution intentionally submitted for inclusion in the Work 133 | by You to the Licensor shall be under the terms and conditions of 134 | this License, without any additional terms or conditions. 135 | Notwithstanding the above, nothing herein shall supersede or modify 136 | the terms of any separate license agreement you may have executed 137 | with Licensor regarding such Contributions. 138 | 139 | 6. Trademarks. This License does not grant permission to use the trade 140 | names, trademarks, service marks, or product names of the Licensor, 141 | except as required for reasonable and customary use in describing the 142 | origin of the Work and reproducing the content of the NOTICE file. 143 | 144 | 7. Disclaimer of Warranty. Unless required by applicable law or 145 | agreed to in writing, Licensor provides the Work (and each 146 | Contributor provides its Contributions) on an "AS IS" BASIS, 147 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 148 | implied, including, without limitation, any warranties or conditions 149 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 150 | PARTICULAR PURPOSE. You are solely responsible for determining the 151 | appropriateness of using or redistributing the Work and assume any 152 | risks associated with Your exercise of permissions under this License. 153 | 154 | 8. Limitation of Liability. In no event and under no legal theory, 155 | whether in tort (including negligence), contract, or otherwise, 156 | unless required by applicable law (such as deliberate and grossly 157 | negligent acts) or agreed to in writing, shall any Contributor be 158 | liable to You for damages, including any direct, indirect, special, 159 | incidental, or consequential damages of any character arising as a 160 | result of this License or out of the use or inability to use the 161 | Work (including but not limited to damages for loss of goodwill, 162 | work stoppage, computer failure or malfunction, or any and all 163 | other commercial damages or losses), even if such Contributor 164 | has been advised of the possibility of such damages. 165 | 166 | 9. Accepting Warranty or Additional Liability. While redistributing 167 | the Work or Derivative Works thereof, You may choose to offer, 168 | and charge a fee for, acceptance of support, warranty, indemnity, 169 | or other liability obligations and/or rights consistent with this 170 | License. However, in accepting such obligations, You may act only 171 | on Your own behalf and on Your sole responsibility, not on behalf 172 | of any other Contributor, and only if You agree to indemnify, 173 | defend, and hold each Contributor harmless for any liability 174 | incurred by, or claims asserted against, such Contributor by reason 175 | of your accepting any such warranty or additional liability. 176 | 177 | END OF TERMS AND CONDITIONS 178 | 179 | APPENDIX: How to apply the Apache License to your work. 180 | 181 | To apply the Apache License to your work, attach the following 182 | boilerplate notice, with the fields enclosed by brackets "[]" 183 | replaced with your own identifying information. (Don't include 184 | the brackets!) The text should be enclosed in the appropriate 185 | comment syntax for the file format. We also recommend that a 186 | file or class name and description of purpose be included on the 187 | same "printed page" as the copyright notice for easier 188 | identification within third-party archives. 189 | 190 | Copyright [2016-] [Tomo Krajina] 191 | 192 | Licensed under the Apache License, Version 2.0 (the "License"); 193 | you may not use this file except in compliance with the License. 194 | You may obtain a copy of the License at 195 | 196 | http://www.apache.org/licenses/LICENSE-2.0 197 | 198 | Unless required by applicable law or agreed to in writing, software 199 | distributed under the License is distributed on an "AS IS" BASIS, 200 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 201 | See the License for the specific language governing permissions and 202 | limitations under the License. 203 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Golang reflector 2 | 3 | First of all, don't use reflection if you don't have to. 4 | 5 | But if you really have to... This library offers a simplified Golang reflection abstraction. 6 | 7 | ## Getting and setting fields 8 | 9 | Let's suppose we have structs like: 10 | 11 | type Address struct { 12 | Street string `tag:"be" tag2:"1,2,3"` 13 | Number int `tag:"bi"` 14 | } 15 | 16 | type Person struct { 17 | Name string `tag:"bu"` 18 | Address 19 | } 20 | 21 | func (p Person) Hi(name string) string { 22 | return fmt.Sprintf("Hi %s my name is %s", name, p.Name) 23 | } 24 | 25 | Initialize the **reflector**'s object wrapper: 26 | 27 | import "github.com/tkrajina/go-reflector/reflector" 28 | 29 | p := Person{} 30 | obj := reflector.New(p) 31 | 32 | Check if a field is valid: 33 | 34 | obj.Field("Name").IsValid() 35 | 36 | Get field value: 37 | 38 | val, err := obj.Field("Name").Get() 39 | 40 | Set field value: 41 | 42 | p := Person{} 43 | obj := reflector.New(&p) 44 | err := obj.Field("Name").Set("Something") 45 | 46 | Don't forget to use a pointer in `New()`, otherwise setters won't work. Field "settability" can be checked by using `field.IsSettable()`. 47 | 48 | ## Tags 49 | 50 | Get a tag: 51 | 52 | jsonTag := obj.Field("Name").Tag("json") 53 | 54 | Get tag values array (exploded with "," as a delimiter): 55 | 56 | jsonTag := obj.Field("Name").TagExpanded("json") 57 | 58 | Or get a map with all field tags: 59 | 60 | fieldTagsMap := obj.Field("Name").Tags() 61 | 62 | ## Listing fields 63 | 64 | There are three ways to list fields: 65 | 66 | * List all fields: This will include anonymous structs **and** fields declared in anonymous structs (`Name`, `Address`, `Street`, `Number`). 67 | * List flattened fields: Includes fields declared in anonymous structs **without** anonymous structs (`Name`, `Street`, `Number`). 68 | * List nonflattened fields: Includes anonymous structs **without** their fields (`Name`, `Address`). This is the way fields are actually declared in the code. 69 | 70 | Depending on which listing you want, you can use: 71 | 72 | fields := obj.FieldsAll() 73 | fields := obj.FieldsFlattened() 74 | fields := obj.Fields() 75 | 76 | You can only get the list of anonymous fields with `obj.FieldsAnonymous()`. 77 | 78 | Be aware that because of anonymous structs, some field names can be returned twice! 79 | In most cases this is not a desired situation, but you can use **reflector** to detect such situations in your code: 80 | 81 | doubleDeclaredFields := obj.FindDoubleFields() 82 | if len(doubleDeclaredFields) > 0 { 83 | fmt.Println("Detected multiple fields with same name:", doubleDeclaredFields) 84 | } 85 | 86 | The field listing will contain both exported and unexported fields. Unexported fields are not gettable/settable, but their tags are readable. 87 | 88 | ## Calling methods 89 | 90 | obj := reflector.New(&Person{}) 91 | resp, err := obj.Method("Hi").Call("John", "Smith") 92 | 93 | The `err` is not `nil` only if something was wrong with the method (for example invalid method name, or wrong argument number/types), not with the actual method call. 94 | If the call finished, `err` will be `nil`. 95 | If the method call returned an error, you can check it with: 96 | 97 | if resp.IsError() { 98 | fmt.Println("Got an error:", resp.Error.Error()) 99 | } else { 100 | fmt.Println("Method call response:", resp.Result) 101 | } 102 | 103 | ## Listing methods 104 | 105 | for _, method := range obj.Methods() { 106 | fmt.Println("Method", method.Name(), "with input types", method.InTypes(), "and output types", method.OutTypes()) 107 | } 108 | 109 | ## Getting length, getting and setting slice/array/string/map elements 110 | 111 | Map: 112 | 113 | m := map[string]interface{}{"aaa", 17} 114 | o := reflector.New(m) 115 | fmt.Println("Length", o.Len()) 116 | val, found := o.GetByKey("aaa") 117 | o.SetByKey("bbb", "new value") 118 | fmt.Println("keys:", o.Keys()) 119 | 120 | Slice, string: 121 | 122 | l := []int{1, 2, 3} 123 | o := reflector.New(o) 124 | fmt.Println("Length", o.Len()) 125 | val, found := o.GetByIndex(0) 126 | o.SetByIndex(0, 19) 127 | 128 | ## Performance 129 | 130 | When reflecting the same type multiple times, **reflector** will cache as much reflection metadata as possible **only once** and use that in future. 131 | 132 | If you make any changes to the library, run `make test-performance` to check performance improvement/deterioration before/after your change. 133 | 134 | $ make test-performance 135 | N=1000000 go test -v ./... -run=TestPerformance 136 | === RUN TestPerformance 137 | WITH REFLECTION 138 | n= 1000000 139 | started: 2016-05-25 08:35:15.5258 140 | ended: 2016-05-25 08:35:19.5258 141 | duration: 4.269112s 142 | --- PASS: TestPerformance (4.27s) 143 | === RUN TestPerformancePlain 144 | WITHOUT REFLECTION 145 | n= 1000000 146 | started: 2016-05-25 08:35:19.5258 147 | ended: 2016-05-25 08:35:19.5258 148 | duration: 0.005237s 149 | --- PASS: TestPerformancePlain (0.01s) 150 | PASS 151 | ok github.com/tkrajina/go-reflector/reflector 4.285s 152 | 153 | Keep those numbers in mind before deciding to use reflection :) 154 | 155 | License 156 | ------- 157 | 158 | **Reflector** is licensed under the [Apache License, Version 2.0](http://www.apache.org/licenses/LICENSE-2.0) 159 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/tkrajina/go-injector 2 | 3 | go 1.17 4 | 5 | require ( 6 | github.com/stretchr/testify v1.7.0 7 | github.com/tkrajina/go-reflector v0.5.8 8 | ) 9 | 10 | require ( 11 | github.com/davecgh/go-spew v1.1.0 // indirect 12 | github.com/pmezard/go-difflib v1.0.0 // indirect 13 | gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c // indirect 14 | ) 15 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8= 2 | github.com/davecgh/go-spew v1.1.0/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/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 6 | github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY= 7 | github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= 8 | github.com/tkrajina/go-reflector v0.5.8 h1:yPADHrwmUbMq4RGEyaOUpz2H90sRsETNVpjzo3DLVQQ= 9 | github.com/tkrajina/go-reflector v0.5.8/go.mod h1:ECbqLgccecY5kPmPmXg1MrHW585yMcDkVl6IvJe64T4= 10 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= 11 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 12 | gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo= 13 | gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 14 | -------------------------------------------------------------------------------- /makefile: -------------------------------------------------------------------------------- 1 | .PHONY: test 2 | test: lint 3 | go test -race ./... 4 | 5 | .PHONY: test-performance 6 | test-performance: 7 | N=1000000 go test -race -v ./... -run=TestPerformance 8 | 9 | .PHONY: install 10 | install: check test 11 | go install ./... 12 | 13 | .PHONY: lint 14 | lint: 15 | go vet ./... 16 | golangci-lint run 17 | 18 | .PHONY: install-tools 19 | install-tools: 20 | go get -u github.com/fzipp/gocyclo 21 | go get -u github.com/golang/lint 22 | go get -u github.com/kisielk/errcheck 23 | -------------------------------------------------------------------------------- /reflector/performance_test.go: -------------------------------------------------------------------------------- 1 | package reflector 2 | 3 | import ( 4 | "fmt" 5 | "os" 6 | "strconv" 7 | "testing" 8 | "time" 9 | ) 10 | 11 | func stopwatch(n int, title string) func() { 12 | start := time.Now() 13 | return func() { 14 | fmt.Println(title) 15 | fmt.Printf("%12s= %d\n", "n", n) 16 | const dateFormat = "2006-01-02 15:04:05.123" 17 | fmt.Printf("%12s: %s\n", "started", start.Format(dateFormat)) 18 | fmt.Printf("%12s: %fs\n", "duration", time.Since(start).Seconds()) 19 | } 20 | } 21 | 22 | func performanceN(envN string) int { 23 | n, _ := strconv.ParseInt(envN, 10, 64) 24 | const defaultN = 1000 25 | if n < 1 { 26 | n = defaultN 27 | } 28 | return int(n) 29 | } 30 | 31 | // Utility to test performance of some typical operations, run with: 32 | // N=1000000 go test -v ./... -run=TestPerformance 33 | // Iy you change anything here, change TestPerformance_plain too! 34 | func TestPerformance_reflection(t *testing.T) { 35 | t.Parallel() 36 | n := performanceN(os.Getenv("N")) 37 | defer stopwatch(n, "WITH REFLECTION")() 38 | for i := 0; i < n; i++ { 39 | p := &Person{} 40 | obj := New(p) 41 | 42 | err := obj.Field("Number").Set(i) 43 | if err != nil { 44 | t.Fatal("Should not error") 45 | } 46 | if p.Number != i { 47 | t.Fatalf("Should be %d", i) 48 | } 49 | number, err := obj.Field("Number").Get() 50 | if err != nil { 51 | t.Fatal("Number is valid") 52 | } 53 | if number.(int) != i { 54 | t.Fatalf("Should be %d", i) 55 | } 56 | res, err := obj.Method("Add").Call(1, 2, 3) 57 | if err != nil { 58 | t.Fatal("shouldn't be an error") 59 | } 60 | if res.IsError() { 61 | t.Fatal("method shouldn't return an error") 62 | } 63 | if len(res.Result) != 1 && res.Result[0].(int) != 6 { 64 | t.Fatal("result should be 6") 65 | } 66 | } 67 | } 68 | 69 | func TestPerformance_plain(t *testing.T) { 70 | t.Parallel() 71 | n := performanceN(os.Getenv("N")) 72 | defer stopwatch(n, "WITHOUT REFLECTION")() 73 | for i := 0; i < n; i++ { 74 | p := &Person{} 75 | 76 | p.Number = i 77 | number := p.Number 78 | if number != i { 79 | t.Fatalf("Should be %d", i) 80 | } 81 | res := p.Add(1, 2, 3) 82 | if res != 6 { 83 | t.Fatal("result should be 6") 84 | } 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /reflector/reflector.go: -------------------------------------------------------------------------------- 1 | package reflector 2 | 3 | import ( 4 | "fmt" 5 | "reflect" 6 | "strings" 7 | "sync" 8 | ) 9 | 10 | type fieldListingType int 11 | 12 | const ( 13 | fieldsAll fieldListingType = iota 14 | fieldsAnonymous 15 | fieldsFlattenAnonymous 16 | fieldsNoFlattenAnonymous 17 | ) 18 | 19 | var ( 20 | metadataCache map[reflect.Type]ObjMetadata 21 | metadataCacheMutex sync.RWMutex 22 | ) 23 | 24 | func init() { 25 | metadataCache = map[reflect.Type]ObjMetadata{} 26 | } 27 | 28 | func updateCache(ty reflect.Type, o *Obj) { 29 | metadataCacheMutex.Lock() 30 | defer metadataCacheMutex.Unlock() 31 | 32 | metadataCache[ty] = o.ObjMetadata 33 | } 34 | 35 | // ObjMetadata contains data which is always unique per Type. 36 | type ObjMetadata struct { 37 | isStruct bool 38 | isPtrToStruct bool 39 | 40 | // If ptr to struct, this field will contain the type of that struct 41 | underlyingType reflect.Type 42 | 43 | objType reflect.Type 44 | objKind reflect.Kind 45 | 46 | fields map[string]ObjFieldMetadata 47 | 48 | fieldNamesAll []string 49 | fieldNamesAnonymous []string 50 | fieldNamesFlattenAnonymous []string 51 | fieldNamesNoFlattenAnonymous []string 52 | 53 | methods map[string]ObjMethodMetadata 54 | methodNames []string 55 | } 56 | 57 | func newObjMetadata(ty reflect.Type) *ObjMetadata { 58 | res := new(ObjMetadata) 59 | if ty == nil { 60 | res.objKind = reflect.Invalid 61 | return res 62 | } 63 | 64 | res.objType = ty 65 | res.objKind = res.objType.Kind() 66 | 67 | if ty.Kind() == reflect.Struct { 68 | res.isStruct = true 69 | } 70 | if ty.Kind() == reflect.Ptr && ty.Elem().Kind() == reflect.Struct { 71 | ty = ty.Elem() 72 | res.isPtrToStruct = true 73 | } 74 | res.underlyingType = ty 75 | 76 | allFields := res.getFields(res.objType, fieldsAll) 77 | 78 | res.fieldNamesAll = allFields 79 | res.fieldNamesAnonymous = res.getFields(res.objType, fieldsAnonymous) 80 | res.fieldNamesFlattenAnonymous = res.getFields(res.objType, fieldsFlattenAnonymous) 81 | res.fieldNamesNoFlattenAnonymous = res.getFields(res.objType, fieldsNoFlattenAnonymous) 82 | 83 | res.methods = map[string]ObjMethodMetadata{} 84 | res.methodNames = []string{} 85 | 86 | if res.objKind != reflect.Invalid { 87 | res.fields = map[string]ObjFieldMetadata{} 88 | for _, fieldName := range allFields { 89 | res.fields[fieldName] = *newObjFieldMetadata(res.objType, fieldName, res) 90 | } 91 | for i := 0; i < res.objType.NumMethod(); i++ { 92 | method := res.objType.Method(i) 93 | res.methodNames = append(res.methodNames, method.Name) 94 | res.methods[method.Name] = *newObjMethodMetadata(res.objType, method.Name, res) 95 | } 96 | } 97 | 98 | return res 99 | } 100 | 101 | // IsStructOrPtrToStruct checks if the value is a struct or a pointer to a struct. 102 | func (om *ObjMetadata) IsStructOrPtrToStruct() bool { 103 | return om.isStruct || om.isPtrToStruct 104 | } 105 | 106 | func (om *ObjMetadata) appendFields(fields []string, field reflect.StructField, listingType fieldListingType) []string { 107 | k := field.Type.Kind() 108 | if listingType == fieldsAnonymous { 109 | if field.Anonymous { 110 | fields = append(fields, field.Name) 111 | } 112 | } else if listingType == fieldsAll { 113 | fields = append(fields, field.Name) 114 | if k == reflect.Struct && field.Anonymous { 115 | fields = append(fields, om.getFields(field.Type, listingType)...) 116 | } 117 | } else { 118 | if listingType == fieldsFlattenAnonymous && k == reflect.Struct && field.Anonymous { 119 | fields = append(fields, om.getFields(field.Type, listingType)...) 120 | } else { 121 | fields = append(fields, field.Name) 122 | } 123 | } 124 | return fields 125 | } 126 | 127 | func (om *ObjMetadata) getFields(ty reflect.Type, listingType fieldListingType) []string { 128 | var fields []string 129 | 130 | if ty.Kind() == reflect.Ptr { 131 | ty = ty.Elem() 132 | } 133 | 134 | if ty.Kind() != reflect.Struct { 135 | return fields // No need to populate nonstructs 136 | } 137 | 138 | for i := 0; i < ty.NumField(); i++ { 139 | f := ty.Field(i) 140 | fields = om.appendFields(fields, f, listingType) 141 | } 142 | 143 | return fields 144 | } 145 | 146 | // ObjFieldMetadata contains data which is always unique per Type/Field. 147 | type ObjFieldMetadata struct { 148 | name string 149 | 150 | structField reflect.StructField 151 | 152 | // Valid here is not yet the final info about an actual field validity, 153 | // because value field still have .IsValid() 154 | valid bool 155 | 156 | fieldKind reflect.Kind 157 | fieldType reflect.Type 158 | } 159 | 160 | func newObjFieldMetadata(ty reflect.Type, name string, objMetadata *ObjMetadata) *ObjFieldMetadata { 161 | res := &ObjFieldMetadata{} 162 | res.fieldKind = reflect.Invalid 163 | res.name = name 164 | if objMetadata.IsStructOrPtrToStruct() { 165 | var found bool 166 | var structField reflect.StructField 167 | if objMetadata.isPtrToStruct { 168 | structField, found = objMetadata.objType.Elem().FieldByName(res.name) 169 | } else { 170 | structField, found = objMetadata.objType.FieldByName(res.name) 171 | } 172 | res.structField = structField 173 | res.fieldType = structField.Type 174 | if res.fieldType == nil { 175 | res.valid = false 176 | } else { 177 | res.fieldKind = structField.Type.Kind() 178 | res.valid = found 179 | } 180 | } 181 | return res 182 | } 183 | 184 | // ObjMethodMetadata contains data 185 | // which is always unique per Type/Method. 186 | type ObjMethodMetadata struct { 187 | name string 188 | method reflect.Method 189 | valid bool 190 | } 191 | 192 | func newObjMethodMetadata(ty reflect.Type, name string, objMetadata *ObjMetadata) *ObjMethodMetadata { 193 | res := &ObjMethodMetadata{name: name} 194 | 195 | if objMetadata.objKind == reflect.Invalid { 196 | res.valid = false 197 | } else { 198 | if method, found := objMetadata.objType.MethodByName(name); found { 199 | res.method = method 200 | res.valid = res.method.Func.IsValid() 201 | } else { 202 | res.valid = false 203 | } 204 | } 205 | 206 | return res 207 | } 208 | 209 | // Obj is a wrapper for golang values which need to be reflected. 210 | // The value can be of any kind and any type. 211 | type Obj struct { 212 | iface interface{} 213 | // Value used to work with fields. The only special case is when iface is a pointer to a struct, in 214 | // that case this is the value of that struct: 215 | fieldsValue reflect.Value 216 | ObjMetadata 217 | } 218 | 219 | // NewFromType creates a new Obj but using reflect.Type. 220 | func NewFromType(ty reflect.Type) *Obj { 221 | if ty == nil { 222 | return New(nil) 223 | } 224 | return New(reflect.New(ty).Interface()) 225 | } 226 | 227 | // New initializes a new Obj wrapper. 228 | func New(obj interface{}) *Obj { 229 | o := &Obj{iface: obj} 230 | 231 | ty := reflect.TypeOf(obj) 232 | metadataCacheMutex.RLock() 233 | metadata, found := metadataCache[ty] 234 | metadataCacheMutex.RUnlock() 235 | if found { 236 | o.ObjMetadata = metadata 237 | } else { 238 | o.ObjMetadata = *newObjMetadata(reflect.TypeOf(obj)) 239 | updateCache(ty, o) 240 | } 241 | 242 | o.fieldsValue = reflect.Indirect(reflect.ValueOf(obj)) 243 | 244 | return o 245 | } 246 | 247 | // IsValid checks if the underlying objects is valid. 248 | // Nil is an invalid value, for example. 249 | func (o *Obj) IsValid() bool { 250 | return o.objKind != reflect.Invalid 251 | } 252 | 253 | // Len returns object length. Works for arrays, channels, maps, slices and strings. 254 | // 255 | // It doesn't panic for other types, returns 0 instead. 256 | // 257 | // In case the value is a pointer, len checks the underlying value. 258 | func (o *Obj) Len() int { 259 | switch o.fieldsValue.Kind() { 260 | case reflect.Array, reflect.Chan, reflect.Map, reflect.Slice, reflect.String: 261 | return o.fieldsValue.Len() 262 | } 263 | return 0 264 | } 265 | 266 | // IsGettableByIndex returns true if underlying type is array, slice or string 267 | func (o *Obj) IsGettableByIndex() bool { 268 | switch o.fieldsValue.Kind() { 269 | case reflect.Array, reflect.Slice, reflect.String: 270 | return true 271 | default: 272 | return false 273 | } 274 | } 275 | 276 | // IsSettableByIndex returns true if underlying type is array, slice (but not string, since they are immutable) 277 | func (o *Obj) IsSettableByIndex() bool { 278 | switch o.fieldsValue.Kind() { 279 | case reflect.Array, reflect.Slice: 280 | return true 281 | default: 282 | return false 283 | } 284 | } 285 | 286 | // IsMap returns true if underlying type is map or a pointer to a map 287 | func (o *Obj) IsMap() bool { 288 | switch o.fieldsValue.Kind() { 289 | case reflect.Map: 290 | return true 291 | default: 292 | return false 293 | } 294 | } 295 | 296 | // GetByIndex returns a value by int index. 297 | // 298 | // Works for arrays, slices and strins. Won't panic when index or kind is invalid. 299 | func (o *Obj) GetByIndex(index int) (value interface{}, found bool) { 300 | defer func() { 301 | if err := recover(); err != nil { 302 | value = nil 303 | found = false 304 | } 305 | }() 306 | 307 | if o.IsGettableByIndex() { 308 | if 0 <= index && index < o.fieldsValue.Len() { 309 | value = o.fieldsValue.Index(index).Interface() 310 | found = true 311 | return 312 | } 313 | } 314 | 315 | return 316 | } 317 | 318 | // SetByIndex sets a slice value by key. 319 | func (o *Obj) SetByIndex(index int, val interface{}) error { 320 | if index < 0 || o.Len() <= index { 321 | return fmt.Errorf("cannot set element %d", index) 322 | } 323 | 324 | if o.IsSettableByIndex() { 325 | elem := o.fieldsValue.Index(index) 326 | elem.Set(reflect.ValueOf(val)) 327 | return nil 328 | } 329 | 330 | return fmt.Errorf("cannot set element %d of %s", index, o.fieldsValue.String()) 331 | } 332 | 333 | // Keys return map keys in unspecified order. 334 | func (o *Obj) Keys() ([]interface{}, error) { 335 | if o.IsMap() { 336 | keys := o.fieldsValue.MapKeys() 337 | res := make([]interface{}, len(keys)) 338 | for n := range keys { 339 | res[n] = keys[n].Interface() 340 | } 341 | return res, nil 342 | } 343 | return nil, fmt.Errorf("invalid type %s", o.Type().String()) 344 | } 345 | 346 | // SetByKey sets a map value by key. 347 | func (o *Obj) SetByKey(key interface{}, val interface{}) (err error) { 348 | defer func() { 349 | if e := recover(); e != nil { 350 | err = fmt.Errorf("cannot set key %s: %v", key, e) 351 | } 352 | }() 353 | 354 | if o.IsMap() { 355 | o.fieldsValue.SetMapIndex(reflect.ValueOf(key), reflect.ValueOf(val)) 356 | return 357 | } 358 | err = fmt.Errorf("cannot set key %s: %w", key, err) 359 | return 360 | } 361 | 362 | // GetByKey returns a value by map key. 363 | // 364 | // Won't panic when key is invalid or kind is not map. 365 | func (o *Obj) GetByKey(key interface{}) (value interface{}, found bool) { 366 | defer func() { 367 | if err := recover(); err != nil { 368 | value = nil 369 | found = false 370 | } 371 | }() 372 | 373 | if o.IsMap() { 374 | v := o.fieldsValue.MapIndex(reflect.ValueOf(key)) 375 | if !v.IsValid() { 376 | found = false 377 | return 378 | } 379 | value = v.Interface() 380 | found = true 381 | return 382 | } 383 | return 384 | } 385 | 386 | // Fields returns fields. 387 | // Don't list fields inside Anonymous fields as distinct fields. 388 | func (o *Obj) Fields() []ObjField { 389 | return o.getFields(fieldsNoFlattenAnonymous) 390 | } 391 | 392 | // FieldsFlattened returns fields. 393 | // Will not list Anonymous fields but it will list fields declared in those anonymous fields. 394 | func (o Obj) FieldsFlattened() []ObjField { 395 | return o.getFields(fieldsFlattenAnonymous) 396 | } 397 | 398 | // FieldsAll returns fields. 399 | // List both anonymous fields and fields declared inside anonymous fields. 400 | func (o Obj) FieldsAll() []ObjField { 401 | return o.getFields(fieldsAll) 402 | } 403 | 404 | // FieldsAnonymous returns only anonymous fields. 405 | func (o Obj) FieldsAnonymous() []ObjField { 406 | return o.getFields(fieldsAnonymous) 407 | } 408 | 409 | func (o *Obj) getFields(listingType fieldListingType) []ObjField { 410 | var fieldNames []string 411 | switch listingType { 412 | case fieldsAll: 413 | fieldNames = o.fieldNamesAll 414 | case fieldsAnonymous: 415 | fieldNames = o.fieldNamesAnonymous 416 | case fieldsFlattenAnonymous: 417 | fieldNames = o.fieldNamesFlattenAnonymous 418 | case fieldsNoFlattenAnonymous: 419 | fieldNames = o.fieldNamesNoFlattenAnonymous 420 | default: 421 | panic(fmt.Sprintf("Invalid field listing type %d", listingType)) 422 | } 423 | 424 | res := make([]ObjField, len(fieldNames)) 425 | for n, fieldName := range fieldNames { 426 | res[n] = *o.Field(fieldName) 427 | } 428 | 429 | return res 430 | } 431 | 432 | // FindDoubleFields checks if this object has declared 433 | // multiple fields with a same name. 434 | // (by checking recursively Anonymous fields and their fields) 435 | func (o Obj) FindDoubleFields() []string { 436 | fields := map[string]int{} 437 | res := []string{} 438 | for _, f := range o.FieldsAll() { 439 | counter := fields[f.name] 440 | if counter == 1 { 441 | res = append(res, f.name) 442 | } 443 | fields[f.name] = counter + 1 444 | } 445 | return res 446 | } 447 | 448 | // IsPtr checks if the value is a pointer. 449 | func (o Obj) IsPtr() bool { 450 | return o.objKind == reflect.Ptr 451 | } 452 | 453 | func (o Obj) Dereferenced() interface{} { 454 | val := o.fieldsValue 455 | for val.Kind() == reflect.Ptr { 456 | val = val.Elem() 457 | } 458 | return val.Interface() 459 | } 460 | 461 | // Field get a field wrapper. 462 | // Note that the field name can be invalid. 463 | // You can check the field validity using ObjField.IsValid(). 464 | func (o *Obj) Field(fieldName string) *ObjField { 465 | if o.fieldsValue.IsValid() { 466 | if metadata, found := o.fields[fieldName]; found { 467 | return newObjField(o, metadata) 468 | } 469 | } 470 | return newObjField(o, ObjFieldMetadata{name: fieldName, valid: false, fieldKind: reflect.Invalid}) 471 | } 472 | 473 | // Type returns the value type. 474 | // If kind is invalid, this will return a zero filled reflect.Type. 475 | func (o Obj) Type() reflect.Type { 476 | return o.objType 477 | } 478 | 479 | // Kind returns the value's kind. 480 | func (o Obj) Kind() reflect.Kind { 481 | return o.objKind 482 | } 483 | 484 | func (o Obj) String() string { 485 | if o.objType == nil { 486 | return "nil" 487 | } 488 | return o.objType.String() 489 | } 490 | 491 | // Method returns a new method wrapper. 492 | // The method name can be invalid, check the method validity with ObjMethod.IsValid(). 493 | func (o *Obj) Method(name string) *ObjMethod { 494 | if metadata, found := o.methods[name]; found { 495 | return newObjMethod(o, metadata) 496 | } 497 | return newObjMethod(o, ObjMethodMetadata{name: name, valid: false}) 498 | } 499 | 500 | // Methods returns the list of all methods. 501 | func (o *Obj) Methods() []ObjMethod { 502 | res := make([]ObjMethod, 0, len(o.methodNames)) 503 | for _, name := range o.methodNames { 504 | res = append(res, *o.Method(name)) 505 | } 506 | return res 507 | } 508 | 509 | // ObjField is a wrapper for the object's field. 510 | type ObjField struct { 511 | obj *Obj 512 | value reflect.Value 513 | 514 | ObjFieldMetadata 515 | } 516 | 517 | func newObjField(obj *Obj, metadata ObjFieldMetadata) *ObjField { 518 | res := &ObjField{ 519 | obj: obj, 520 | ObjFieldMetadata: metadata, 521 | } 522 | 523 | if metadata.valid && res.obj.IsStructOrPtrToStruct() { 524 | res.value = obj.fieldsValue.FieldByName(res.name) 525 | } 526 | 527 | return res 528 | } 529 | 530 | func (of *ObjField) assertValid() error { 531 | if !of.IsValid() { 532 | return fmt.Errorf("invalid field %s", of.name) 533 | } 534 | return nil 535 | } 536 | 537 | // IsValid checks if the fields is valid. 538 | func (of *ObjField) IsValid() bool { 539 | return of.valid && of.value.IsValid() 540 | } 541 | 542 | // Name returns the field's name. 543 | func (of *ObjField) Name() string { 544 | return of.name 545 | } 546 | 547 | // Kind returns the field's kind. 548 | func (of *ObjField) Kind() reflect.Kind { 549 | return of.fieldKind 550 | } 551 | 552 | // Type returns the field's type. 553 | func (of *ObjField) Type() reflect.Type { 554 | return of.fieldType 555 | } 556 | 557 | // Tag returns the value of this specific tag 558 | // or error if the field is invalid. 559 | func (of *ObjField) Tag(tag string) (string, error) { 560 | if err := of.assertValid(); err != nil { 561 | return "", err 562 | } 563 | return of.structField.Tag.Get(tag), nil 564 | } 565 | 566 | // Tags returns the map of all fields or error for invalid field. 567 | func (of *ObjField) Tags() (map[string]string, error) { 568 | if err := of.assertValid(); err != nil { 569 | return nil, err 570 | } 571 | 572 | tag := of.structField.Tag 573 | return ParseTag(string(tag)) 574 | } 575 | 576 | // TagsString returns the complete tags string (everything inside “) 577 | func (of *ObjField) TagsString() (string, error) { 578 | if err := of.assertValid(); err != nil { 579 | return "", err 580 | } 581 | 582 | return string(of.structField.Tag), nil 583 | } 584 | 585 | // TagExpanded returns the tag value "expanded" with commas. 586 | func (of *ObjField) TagExpanded(tag string) ([]string, error) { 587 | if err := of.assertValid(); err != nil { 588 | return nil, err 589 | } 590 | return strings.Split(of.structField.Tag.Get(tag), ","), nil 591 | } 592 | 593 | // IsAnonymous checks if this is an anonymous (embedded) field. 594 | func (of *ObjField) IsAnonymous() bool { 595 | if err := of.assertValid(); err != nil { 596 | return false 597 | } 598 | field, found := of.obj.underlyingType.FieldByName(of.name) 599 | if !found { 600 | return false 601 | } 602 | return field.Anonymous 603 | } 604 | 605 | // IsExported returns true if the name starts with uppercase (i.e. field is public). 606 | func (of *ObjField) IsExported() bool { 607 | return of.structField.PkgPath == "" 608 | } 609 | 610 | // IsSettable checks if this field is settable. 611 | func (of *ObjField) IsSettable() bool { 612 | return of.value.CanSet() 613 | } 614 | 615 | // Set sets a value for this field or error if field is invalid (or not settable). 616 | func (of *ObjField) Set(value interface{}) error { 617 | if err := of.assertValid(); err != nil { 618 | return err 619 | } 620 | 621 | if !of.IsSettable() { 622 | return fmt.Errorf("field %s in %T not settable", of.name, of.obj.iface) 623 | } 624 | 625 | of.value.Set(reflect.ValueOf(value)) 626 | 627 | return nil 628 | } 629 | 630 | // Get gets the field value of error if field is invalid). 631 | func (of *ObjField) Get() (interface{}, error) { 632 | if err := of.assertValid(); err != nil { 633 | return nil, err 634 | } 635 | if !of.IsExported() { 636 | return nil, fmt.Errorf("cannot read unexported field %T.%s", of.obj.iface, of.name) 637 | } 638 | 639 | return of.value.Interface(), nil 640 | } 641 | 642 | // ObjMethod is a wrapper for an object method. 643 | // The name of the method can be invalid. 644 | type ObjMethod struct { 645 | obj *Obj 646 | ObjMethodMetadata 647 | } 648 | 649 | func newObjMethod(obj *Obj, objMethodMetadata ObjMethodMetadata) *ObjMethod { 650 | return &ObjMethod{ 651 | obj: obj, 652 | ObjMethodMetadata: objMethodMetadata, 653 | } 654 | } 655 | 656 | // Name returns the method's name. 657 | func (om *ObjMethod) Name() string { 658 | return om.name 659 | } 660 | 661 | const ( 662 | onlyInTypes = 0 663 | onlyOutTypes = 1 664 | ) 665 | 666 | func (om *ObjMethod) methodTypes(kind int) []reflect.Type { 667 | m := reflect.ValueOf(om.obj.iface).MethodByName(om.name) 668 | if !m.IsValid() { 669 | return []reflect.Type{} 670 | } 671 | ty := m.Type() 672 | 673 | // inTypes are default 674 | tyNum := ty.NumIn() 675 | tyFn := ty.In 676 | if kind == onlyOutTypes { 677 | tyNum = ty.NumOut() 678 | tyFn = ty.Out 679 | } 680 | 681 | out := make([]reflect.Type, tyNum) 682 | for i := 0; i < tyNum; i++ { 683 | out[i] = tyFn(i) 684 | } 685 | return out 686 | } 687 | 688 | // InTypes returns an slice with this method's input types. 689 | func (om *ObjMethod) InTypes() []reflect.Type { 690 | return om.methodTypes(onlyInTypes) 691 | } 692 | 693 | // OutTypes returns an slice with this method's output types. 694 | func (om *ObjMethod) OutTypes() []reflect.Type { 695 | return om.methodTypes(onlyOutTypes) 696 | } 697 | 698 | // IsValid returns this method's validity. 699 | func (om *ObjMethod) IsValid() bool { 700 | return om.valid 701 | } 702 | 703 | // Call calls this method. 704 | // Note that in the error returning value is not the error from the method call. 705 | func (om *ObjMethod) Call(args ...interface{}) (*CallResult, error) { 706 | if !om.obj.IsValid() { 707 | return nil, fmt.Errorf("invalid object type %T for method %s", om.obj.iface, om.name) 708 | } 709 | if !om.IsValid() { 710 | return nil, fmt.Errorf("invalid method %s in %T", om.name, om.obj.iface) 711 | } 712 | in := make([]reflect.Value, len(args)+1) 713 | in[0] = reflect.ValueOf(om.obj.iface) 714 | for n := range args { 715 | in[n+1] = reflect.ValueOf(args[n]) 716 | } 717 | out := om.method.Func.Call(in) 718 | res := make([]interface{}, len(out)) 719 | for n := range out { 720 | res[n] = out[n].Interface() 721 | } 722 | return newCallResult(res), nil 723 | } 724 | 725 | // CallResult is a wrapper of a method call result. 726 | type CallResult struct { 727 | Result []interface{} 728 | Error error 729 | } 730 | 731 | func newCallResult(res []interface{}) *CallResult { 732 | cr := &CallResult{Result: res} 733 | if len(res) == 0 { 734 | return cr 735 | } 736 | errorCandidate := res[len(res)-1] 737 | if errorCandidate != nil { 738 | if err, is := errorCandidate.(error); is { 739 | cr.Error = err 740 | } 741 | } 742 | return cr 743 | } 744 | 745 | // IsError checks if the last value is a non-nil error. 746 | func (cr *CallResult) IsError() bool { 747 | return cr.Error != nil 748 | } 749 | -------------------------------------------------------------------------------- /reflector/reflector_test.go: -------------------------------------------------------------------------------- 1 | package reflector 2 | 3 | import ( 4 | "errors" 5 | "fmt" 6 | "reflect" 7 | "sort" 8 | "testing" 9 | 10 | "github.com/stretchr/testify/assert" 11 | "github.com/tkrajina/go-reflector/reflector/tmp" 12 | ) 13 | 14 | type Address struct { 15 | Street string `tag:"be" tag2:"1,2,3"` 16 | Number int `tag:"bi"` 17 | } 18 | 19 | type Person struct { 20 | Name string `tag:"bu"` 21 | Address 22 | } 23 | 24 | func (p Person) Add(a, b, c int) int { return a + b + c } 25 | func (p *Person) Subtract(a, b int) int { return a - b } 26 | func (p Person) ReturnsError(err bool) (string, *int, error) { 27 | i := 2 28 | if err { 29 | return "", nil, errors.New("error here") 30 | } 31 | return "jen", &i, nil 32 | } 33 | 34 | type CustomType int 35 | 36 | func (ct CustomType) Method1() string { return "yep" } 37 | func (ct *CustomType) Method2() int { return 7 } 38 | 39 | func (p Person) Hi(name string) string { 40 | return fmt.Sprintf("Hi %s my name is %s", name, p.Name) 41 | } 42 | 43 | type Company struct { 44 | Address 45 | Number int `tag:"bi"` 46 | } 47 | 48 | // TestDoubleFields checks that there are no "double" fields (or fields shadowing) 49 | func TestDoubleFields(t *testing.T) { 50 | t.Parallel() 51 | structs := []interface{}{ 52 | Obj{}, 53 | ObjField{}, 54 | ObjMethod{}, 55 | ObjMetadata{}, 56 | ObjFieldMetadata{}, 57 | ObjMethodMetadata{}, 58 | } 59 | for _, s := range structs { 60 | double := New(s).FindDoubleFields() 61 | assert.Equal(t, 0, len(double)) 62 | } 63 | } 64 | 65 | func TestString(t *testing.T) { 66 | t.Parallel() 67 | assert.Equal(t, "reflector.Person", New(Person{}).String()) 68 | assert.Equal(t, "*reflector.Person", New(&Person{}).String()) 69 | assert.Equal(t, "nil", New(nil).String()) 70 | assert.Equal(t, "int", New(1).String()) 71 | var i int 72 | assert.Equal(t, "*int", New(&i).String()) 73 | 74 | // Check that when we twice get a field, the metadata field is cached only once: 75 | assert.Equal(t, New(Person{}).Field("bu").ObjFieldMetadata, New(Person{}).Field("bu").ObjFieldMetadata) 76 | assert.Equal(t, New(&Person{}).Field("bu").ObjFieldMetadata, New(&Person{}).Field("bu").ObjFieldMetadata) 77 | assert.Equal(t, New(Person{}).Field("Address").ObjFieldMetadata, New(Person{}).Field("Address").ObjFieldMetadata) 78 | assert.Equal(t, New(&Person{}).Field("bu").ObjFieldMetadata, New(&Person{}).Field("bu").ObjFieldMetadata) 79 | } 80 | 81 | func TestNilStringPtr(t *testing.T) { 82 | t.Parallel() 83 | assert.Equal(t, "*string", New((*string)(nil)).String()) 84 | assert.Equal(t, 0, len(New((*string)(nil)).Fields())) 85 | assert.Equal(t, 0, len(New((*string)(nil)).Methods())) 86 | 87 | v, err := New((*string)(nil)).Field("Bu").Get() 88 | assert.Nil(t, v) 89 | assert.NotNil(t, err) 90 | } 91 | 92 | func TestNilStructPtr(t *testing.T) { 93 | t.Parallel() 94 | assert.Equal(t, "*reflector.Person", New((*Person)(nil)).String()) 95 | assert.Equal(t, 2, len(New((*Person)(nil)).Fields())) 96 | assert.Equal(t, 4, len(New((*Person)(nil)).Methods())) 97 | 98 | err := New((*Person)(nil)).Field("Number").Set(17) 99 | assert.NotNil(t, err) 100 | 101 | v, err := New((*Person)(nil)).Field("Bu").Get() 102 | assert.Nil(t, v) 103 | assert.NotNil(t, err) 104 | } 105 | 106 | func TestListFieldsFlattened(t *testing.T) { 107 | t.Parallel() 108 | p := Person{} 109 | obj := New(p) 110 | 111 | assert.True(t, obj.IsValid()) 112 | assert.False(t, obj.IsPtr()) 113 | assert.True(t, obj.IsStructOrPtrToStruct()) 114 | 115 | fields := obj.FieldsFlattened() 116 | assert.Equal(t, 3, len(fields)) 117 | assert.Equal(t, fields[0].Name(), "Name") 118 | assert.Equal(t, fields[1].Name(), "Street") 119 | assert.Equal(t, fields[2].Name(), "Number") 120 | 121 | assert.True(t, obj.Field("Name").IsValid()) 122 | 123 | kind := obj.Field("Name").Kind() 124 | assert.Equal(t, reflect.String, kind) 125 | 126 | kind = obj.Field("BuName").Kind() 127 | assert.Equal(t, reflect.Invalid, kind) 128 | 129 | ty := obj.Field("Number").Type() 130 | assert.Equal(t, reflect.TypeOf(1), ty) 131 | 132 | ty = obj.Field("Istra").Type() 133 | assert.Nil(t, ty) 134 | } 135 | 136 | func TestListFields(t *testing.T) { 137 | t.Parallel() 138 | p := Person{} 139 | obj := New(p) 140 | 141 | fields := obj.Fields() 142 | assert.Equal(t, len(fields), 2) 143 | assert.Equal(t, fields[0].Name(), "Name") 144 | assert.Equal(t, fields[1].Name(), "Address") 145 | } 146 | 147 | func TestListFieldsAll(t *testing.T) { 148 | t.Parallel() 149 | p := Person{} 150 | obj := New(p) 151 | 152 | fields := obj.FieldsAll() 153 | assert.Equal(t, len(fields), 4) 154 | assert.Equal(t, fields[0].Name(), "Name") 155 | assert.Equal(t, fields[1].Name(), "Address") 156 | assert.Equal(t, fields[2].Name(), "Street") 157 | assert.Equal(t, fields[3].Name(), "Number") 158 | } 159 | 160 | func TestListFieldsAnonymous(t *testing.T) { 161 | t.Parallel() 162 | p := Person{} 163 | obj := New(p) 164 | 165 | fields := obj.FieldsAnonymous() 166 | assert.Equal(t, 1, len(fields)) 167 | assert.Equal(t, fields[0].Name(), "Address") 168 | } 169 | 170 | func TestListFieldsAllWithDoubleFields(t *testing.T) { 171 | t.Parallel() 172 | obj := New(Company{}) 173 | 174 | fields := obj.FieldsAll() 175 | assert.Equal(t, len(fields), 4) 176 | assert.Equal(t, fields[0].Name(), "Address") 177 | assert.Equal(t, fields[1].Name(), "Street") 178 | // Number is declared both in Company and in Address, so listed twice here: 179 | assert.Equal(t, fields[2].Name(), "Number") 180 | assert.Equal(t, fields[3].Name(), "Number") 181 | } 182 | 183 | func TestFindDoubleFields(t *testing.T) { 184 | t.Parallel() 185 | obj := New(Company{}) 186 | 187 | fields := obj.FindDoubleFields() 188 | assert.Equal(t, 1, len(fields)) 189 | assert.Equal(t, fields[0], "Number") 190 | } 191 | 192 | func TestListFieldsOnPointer(t *testing.T) { 193 | t.Parallel() 194 | p := &Person{} 195 | obj := New(p) 196 | 197 | assert.True(t, obj.IsPtr()) 198 | assert.True(t, obj.IsStructOrPtrToStruct()) 199 | 200 | fields := obj.Fields() 201 | assert.Equal(t, len(fields), 2) 202 | assert.Equal(t, fields[0].Name(), "Name") 203 | assert.Equal(t, fields[1].Name(), "Address") 204 | 205 | kind := obj.Field("Name").Kind() 206 | assert.Equal(t, reflect.String, kind) 207 | 208 | kind = obj.Field("BuName").Kind() 209 | assert.Equal(t, reflect.Invalid, kind) 210 | 211 | ty := obj.Field("Number").Type() 212 | assert.Equal(t, reflect.TypeOf(1), ty) 213 | 214 | ty = obj.Field("Istra").Type() 215 | assert.Nil(t, ty) 216 | } 217 | 218 | func TestListFieldsFlattenedOnPointer(t *testing.T) { 219 | t.Parallel() 220 | p := &Person{} 221 | obj := New(p) 222 | 223 | fields := obj.FieldsFlattened() 224 | assert.Equal(t, len(fields), 3) 225 | assert.Equal(t, fields[0].Name(), "Name") 226 | assert.Equal(t, fields[1].Name(), "Street") 227 | assert.Equal(t, fields[2].Name(), "Number") 228 | } 229 | 230 | func TestNoFieldsNoCustomType(t *testing.T) { 231 | t.Parallel() 232 | assert.Equal(t, len(New(CustomType(1)).Fields()), 0) 233 | ct := CustomType(2) 234 | assert.Equal(t, len(New(&ct).Fields()), 0) 235 | } 236 | 237 | func TestIsStructForCustomTypes(t *testing.T) { 238 | t.Parallel() 239 | ct := CustomType(2) 240 | assert.False(t, New(CustomType(1)).IsPtr()) 241 | assert.True(t, New(&ct).IsPtr()) 242 | assert.False(t, New(CustomType(1)).IsStructOrPtrToStruct()) 243 | assert.False(t, New(&ct).IsStructOrPtrToStruct()) 244 | } 245 | 246 | func TestFieldValidity(t *testing.T) { 247 | t.Parallel() 248 | assert.False(t, New(CustomType(1)).Field("jkljkl").IsValid()) 249 | assert.False(t, New(Person{}).Field("street").IsValid()) 250 | assert.True(t, New(Person{}).Field("Street").IsValid()) 251 | assert.True(t, New(Person{}).Field("Number").IsValid()) 252 | assert.True(t, New(Person{}).Field("Name").IsValid()) 253 | } 254 | 255 | func TestSetFieldNonPointer(t *testing.T) { 256 | t.Parallel() 257 | p := Person{} 258 | obj := New(p) 259 | assert.False(t, obj.IsPtr()) 260 | 261 | field := obj.Field("Street") 262 | assert.False(t, field.IsSettable()) 263 | 264 | err := field.Set("ulica") 265 | assert.Error(t, err) 266 | 267 | value, err := field.Get() 268 | assert.Nil(t, err) 269 | assert.Equal(t, "", value) 270 | 271 | assert.Equal(t, "", p.Street) 272 | 273 | street, err := obj.Field("Street").Get() 274 | assert.Nil(t, err) 275 | 276 | // This actually don't work because p is a struct and reflector is working on it's own copy: 277 | assert.Equal(t, "", street) 278 | 279 | } 280 | 281 | func TestSetField(t *testing.T) { 282 | t.Parallel() 283 | p := Person{} 284 | obj := New(&p) 285 | assert.True(t, obj.IsPtr()) 286 | 287 | field := obj.Field("Street") 288 | assert.True(t, field.IsSettable()) 289 | 290 | err := field.Set("ulica") 291 | assert.Nil(t, err) 292 | 293 | value, err := field.Get() 294 | assert.Nil(t, err) 295 | assert.Equal(t, "ulica", value) 296 | 297 | assert.Equal(t, "ulica", p.Street) 298 | } 299 | 300 | func TestCustomTypeMethods(t *testing.T) { 301 | t.Parallel() 302 | assert.Equal(t, len(New(CustomType(1)).Methods()), 1) 303 | ct := CustomType(1) 304 | assert.Equal(t, len(New(&ct).Methods()), 2) 305 | } 306 | 307 | func TestMethods(t *testing.T) { 308 | t.Parallel() 309 | assert.Equal(t, len(New(Person{}).Methods()), 3) 310 | assert.Equal(t, len(New(&Person{}).Methods()), 4) 311 | 312 | // Check that when we twice get a field, the metadata field is cached only once: 313 | assert.Equal(t, New(Person{}).Method("Add").ObjMethodMetadata, New(Person{}).Method("Add").ObjMethodMetadata) 314 | assert.Equal(t, New(&Person{}).Method("Add").ObjMethodMetadata, New(&Person{}).Method("Add").ObjMethodMetadata) 315 | } 316 | 317 | func TestCallMethod(t *testing.T) { 318 | t.Parallel() 319 | obj := New(&Person{}) 320 | method := obj.Method("Add") 321 | res, err := method.Call(2, 3, 6) 322 | assert.Nil(t, err) 323 | assert.False(t, res.IsError()) 324 | assert.Equal(t, len(res.Result), 1) 325 | assert.Equal(t, res.Result[0], 11) 326 | 327 | assert.True(t, method.IsValid()) 328 | assert.Equal(t, len(method.InTypes()), 3) 329 | assert.Equal(t, len(method.OutTypes()), 1) 330 | 331 | sub, err := obj.Method("Substract").Call(5, 6) 332 | assert.Nil(t, err) 333 | assert.Equal(t, sub.Result, []interface{}{-1}) 334 | } 335 | 336 | func TestCallInvalidMethod(t *testing.T) { 337 | t.Parallel() 338 | obj := New(&Person{}) 339 | method := obj.Method("AddAdddd") 340 | res, err := method.Call([]interface{}{2, 3, 6}) 341 | assert.NotNil(t, err) 342 | assert.Nil(t, res) 343 | 344 | assert.Equal(t, len(method.InTypes()), 0) 345 | assert.Equal(t, len(method.OutTypes()), 0) 346 | } 347 | 348 | func TestMethodsValidityOnPtr(t *testing.T) { 349 | t.Parallel() 350 | ct := CustomType(1) 351 | obj := New(&ct) 352 | 353 | assert.True(t, obj.IsPtr()) 354 | 355 | assert.True(t, obj.Method("Method1").IsValid()) 356 | assert.True(t, obj.Method("Method2").IsValid()) 357 | 358 | { 359 | res, err := obj.Method("Method1").Call() 360 | assert.Nil(t, err) 361 | assert.Equal(t, res.Result, []interface{}{"yep"}) 362 | } 363 | { 364 | res, err := obj.Method("Method2").Call() 365 | assert.Nil(t, err) 366 | assert.Equal(t, res.Result, []interface{}{7}) 367 | } 368 | } 369 | 370 | func TestMethodsValidityOnNonPtr(t *testing.T) { 371 | t.Parallel() 372 | obj := New(CustomType(1)) 373 | 374 | assert.False(t, obj.IsPtr()) 375 | 376 | assert.True(t, obj.Method("Method1").IsValid()) 377 | // False because it's not a pointer 378 | assert.False(t, obj.Method("Method2").IsValid()) 379 | 380 | { 381 | res, err := obj.Method("Method1").Call() 382 | assert.Nil(t, err) 383 | assert.Equal(t, res.Result, []interface{}{"yep"}) 384 | } 385 | { 386 | _, err := obj.Method("Method2").Call() 387 | assert.NotNil(t, err) 388 | } 389 | } 390 | 391 | func testCallMethod(t *testing.T, callValue bool, lenResult int) bool { 392 | obj := New(&Person{}) 393 | res, err := obj.Method("ReturnsError").Call(callValue) 394 | assert.Nil(t, err) 395 | assert.Equal(t, len(res.Result), lenResult) 396 | return res.IsError() 397 | } 398 | 399 | func TestCallMethodWithoutErrResult(t *testing.T) { 400 | t.Parallel() 401 | isErr := testCallMethod(t, true, 3) 402 | assert.True(t, isErr) 403 | } 404 | 405 | func TestCallMethodWithErrResult(t *testing.T) { 406 | t.Parallel() 407 | isErr := testCallMethod(t, false, 3) 408 | assert.False(t, isErr) 409 | } 410 | 411 | func TestTag(t *testing.T) { 412 | t.Parallel() 413 | obj := New(&Person{}) 414 | tag, err := obj.Field("Street").Tag("invalid") 415 | assert.Nil(t, err) 416 | assert.Equal(t, len(tag), 0) 417 | } 418 | 419 | func TestInvalidTag(t *testing.T) { 420 | t.Parallel() 421 | obj := New(&Person{}) 422 | tag, err := obj.Field("HahaStreet").Tag("invalid") 423 | assert.NotNil(t, err) 424 | assert.Equal(t, "invalid field HahaStreet", err.Error()) 425 | assert.Equal(t, len(tag), 0) 426 | } 427 | 428 | func TestValidTag(t *testing.T) { 429 | t.Parallel() 430 | obj := New(&Person{}) 431 | tag, err := obj.Field("Street").Tag("tag") 432 | assert.Nil(t, err) 433 | assert.Equal(t, tag, "be") 434 | } 435 | 436 | func TestValidTags(t *testing.T) { 437 | t.Parallel() 438 | obj := New(&Person{}) 439 | 440 | tags, err := obj.Field("Street").TagExpanded("tag") 441 | assert.Nil(t, err) 442 | assert.Equal(t, tags, []string{"be"}) 443 | 444 | tags2, err := obj.Field("Street").TagExpanded("tag2") 445 | assert.Nil(t, err) 446 | assert.Equal(t, tags2, []string{"1", "2", "3"}) 447 | } 448 | 449 | func TestAllTags(t *testing.T) { 450 | t.Parallel() 451 | obj := New(&Person{}) 452 | 453 | tags, err := obj.Field("Street").Tags() 454 | assert.Nil(t, err) 455 | assert.Equal(t, len(tags), 2) 456 | assert.Equal(t, tags["tag"], "be") 457 | assert.Equal(t, tags["tag2"], "1,2,3") 458 | 459 | /* 460 | type Address struct { 461 | Street string `tag:"be" tag2:"1,2,3"` 462 | Number int `tag:"bi"` 463 | } 464 | */ 465 | fld := New(Address{}).Field("Street") 466 | tagsStr, err := fld.TagsString() 467 | assert.Nil(t, err) 468 | assert.Equal(t, `tag:"be" tag2:"1,2,3"`, tagsStr) 469 | } 470 | 471 | func TestNewFromType(t *testing.T) { 472 | t.Parallel() 473 | obj1 := NewFromType(reflect.TypeOf(Person{})) 474 | obj2 := New(&Person{}) 475 | 476 | assert.Equal(t, obj1.objType.String(), obj2.objType.String()) 477 | assert.Equal(t, obj1.objKind.String(), obj2.objKind.String()) 478 | assert.Equal(t, obj1.underlyingType.String(), obj2.underlyingType.String()) 479 | } 480 | 481 | func TestAnonymousFields(t *testing.T) { 482 | t.Parallel() 483 | obj := New(&Person{}) 484 | 485 | assert.True(t, obj.Field("Address").IsAnonymous()) 486 | assert.False(t, obj.Field("Name").IsAnonymous()) 487 | } 488 | 489 | func TestNil(t *testing.T) { 490 | t.Parallel() 491 | obj := New(nil) 492 | assert.Equal(t, 0, len(obj.Fields())) 493 | assert.Equal(t, 0, len(obj.Methods())) 494 | 495 | res, err := obj.Field("Aaa").Get() 496 | assert.Nil(t, res) 497 | assert.NotNil(t, err) 498 | 499 | err = obj.Field("Aaa").Set(1) 500 | assert.NotNil(t, err) 501 | } 502 | 503 | func TestNilType(t *testing.T) { 504 | t.Parallel() 505 | obj := NewFromType(nil) 506 | assert.Equal(t, 0, len(obj.Fields())) 507 | assert.Equal(t, 0, len(obj.Methods())) 508 | 509 | res, err := obj.Field("Aaa").Get() 510 | assert.Nil(t, res) 511 | assert.NotNil(t, err) 512 | 513 | err = obj.Field("Aaa").Set(1) 514 | assert.NotNil(t, err) 515 | 516 | _, err = obj.Method("aaa").Call("bu") 517 | assert.NotNil(t, err) 518 | } 519 | 520 | func TestStringObj(t *testing.T) { 521 | t.Parallel() 522 | obj := New("") 523 | assert.Equal(t, 0, len(obj.Fields())) 524 | assert.Equal(t, 0, len(obj.Methods())) 525 | 526 | res, err := obj.Field("Aaa").Get() 527 | assert.Nil(t, res) 528 | assert.NotNil(t, err) 529 | 530 | err = obj.Field("Aaa").Set(1) 531 | assert.NotNil(t, err) 532 | 533 | _, err = obj.Method("aaa").Call("bu") 534 | assert.NotNil(t, err) 535 | } 536 | 537 | type TestWithInnerStruct struct { 538 | Aaa string 539 | Bbb struct { 540 | Ccc int 541 | Ddd float64 542 | } 543 | } 544 | 545 | func TestInnerStruct(t *testing.T) { 546 | t.Parallel() 547 | obj := New(TestWithInnerStruct{}) 548 | fields := obj.Fields() 549 | assert.Equal(t, 2, len(fields)) 550 | 551 | assert.Equal(t, "Aaa", fields[0].Name()) 552 | assert.Equal(t, "string", fields[0].Type().String()) 553 | assert.Equal(t, "string", fields[0].Kind().String()) 554 | 555 | assert.Equal(t, "Bbb", fields[1].Name()) 556 | assert.Equal(t, "struct { Ccc int; Ddd float64 }", fields[1].Type().String()) 557 | assert.Equal(t, "struct", fields[1].Kind().String()) 558 | 559 | // This is not an anonymous struct, so fields are always the same: 560 | assert.Equal(t, 2, len(obj.FieldsAll())) 561 | assert.Equal(t, 2, len(obj.FieldsFlattened())) 562 | } 563 | 564 | func TestExportedUnexported(t *testing.T) { 565 | t.Parallel() 566 | obj := New(&tmp.TestStruct{}) 567 | assert.Equal(t, "_", obj.Fields()[0].Name()) 568 | assert.False(t, obj.Fields()[0].IsExported()) 569 | 570 | assert.Equal(t, "Exported", obj.Fields()[1].Name()) 571 | assert.True(t, obj.Fields()[1].IsExported()) 572 | 573 | assert.Equal(t, "unexported", obj.Fields()[2].Name()) 574 | assert.False(t, obj.Fields()[2].IsExported()) 575 | 576 | err := obj.Field("Exported").Set("aaa") 577 | assert.Nil(t, err) 578 | 579 | err = obj.Field("unexported").Set(1777) 580 | assert.NotNil(t, err) 581 | assert.Contains(t, err.Error(), "not settable") 582 | 583 | value, err := obj.Field("unexported").Get() 584 | assert.NotNil(t, err) 585 | assert.Nil(t, value) 586 | assert.Contains(t, err.Error(), "cannot read unexported field") 587 | 588 | // But tags on unexported fields are still readable: 589 | { 590 | tags, err := obj.Field("_").Tags() 591 | assert.Nil(t, err) 592 | assert.Equal(t, 1, len(tags)) 593 | assert.Equal(t, "ba", tags["bu"]) 594 | } 595 | { 596 | tags, err := obj.Field("unexported").Tags() 597 | assert.Nil(t, err) 598 | assert.Equal(t, 2, len(tags)) 599 | } 600 | } 601 | 602 | func TestStringLen(t *testing.T) { 603 | s := "jkljk" 604 | assert.Equal(t, len(s), New(s).Len()) 605 | assert.Equal(t, len(s), New(&s).Len()) 606 | } 607 | 608 | func TestLenOfInvalidTypes(t *testing.T) { 609 | assert.Equal(t, 0, New(&tmp.TestStruct{}).Len()) 610 | assert.Equal(t, 0, New(tmp.TestStruct{}).Len()) 611 | 612 | { 613 | val, found := New(tmp.TestStruct{}).GetByIndex(20) 614 | assert.False(t, found) 615 | assert.Nil(t, val) 616 | } 617 | { 618 | val, found := New(tmp.TestStruct{}).GetByKey("nothing") 619 | assert.False(t, found) 620 | assert.Equal(t, nil, val) 621 | } 622 | { 623 | keys, err := New(tmp.TestStruct{}).Keys() 624 | assert.Nil(t, keys) 625 | assert.NotNil(t, err) 626 | } 627 | } 628 | 629 | func TestSlice(t *testing.T) { 630 | { 631 | a := []int{1, 2, 3, 4, 8, 7, 6, 5} 632 | assert.Equal(t, len(a), New(a).Len()) 633 | assert.Equal(t, len(a), New(&a).Len()) 634 | { 635 | o := New(&a) 636 | assert.Nil(t, o.SetByIndex(1, 1000)) 637 | assert.Equal(t, []int{1, 1000, 3, 4, 8, 7, 6, 5}, a) 638 | } 639 | { 640 | o := New(&a) 641 | assert.NotNil(t, o.SetByIndex(-1, 1000)) 642 | assert.NotNil(t, o.SetByIndex(700, 1000)) 643 | } 644 | { 645 | o := New(a) 646 | assert.Nil(t, o.SetByIndex(1, 1000)) 647 | assert.Equal(t, []int{1, 1000, 3, 4, 8, 7, 6, 5}, a) 648 | } 649 | { 650 | val, found := New(a).GetByIndex(2) 651 | assert.True(t, found) 652 | assert.Equal(t, val, 3) 653 | } 654 | { 655 | val, found := New(a).GetByIndex(20) 656 | assert.False(t, found) 657 | assert.Nil(t, val) 658 | } 659 | { 660 | val, found := New(a).GetByIndex(-20) 661 | assert.False(t, found) 662 | assert.Nil(t, val) 663 | } 664 | } 665 | } 666 | 667 | func TestMapLenGetSet(t *testing.T) { 668 | m := map[string]interface{}{"jkljk": 8, "11": 13, "12": nil} 669 | assert.Equal(t, len(m), New(m).Len()) 670 | assert.Equal(t, len(m), New(&m).Len()) 671 | { 672 | keys, err := New(&m).Keys() 673 | assert.Nil(t, err) 674 | assert.Equal(t, 3, len(keys)) 675 | keysStrings := make([]string, len(keys)) 676 | for n := range keys { 677 | keysStrings[n] = keys[n].(string) 678 | } 679 | sort.Strings(keysStrings) 680 | assert.Equal(t, []string{"11", "12", "jkljk"}, keysStrings) 681 | } 682 | { 683 | val, found := New(&m).GetByKey("11") 684 | assert.True(t, found) 685 | assert.Equal(t, 13, val) 686 | } 687 | { 688 | val, found := New(m).GetByKey("11") 689 | assert.True(t, found) 690 | assert.Equal(t, 13, val) 691 | } 692 | { 693 | val, found := New(m).GetByKey("12") 694 | assert.True(t, found) 695 | assert.Equal(t, nil, val) 696 | } 697 | { 698 | val, found := New(m).GetByKey("nothing") 699 | assert.False(t, found) 700 | assert.Equal(t, nil, val) 701 | } 702 | { 703 | val, found := New(m).GetByKey(17) 704 | assert.False(t, found) 705 | assert.Equal(t, nil, val) 706 | } 707 | 708 | } 709 | 710 | func TestMapSet(t *testing.T) { 711 | m := map[string]interface{}{"jkljk": 8, "11": 13, "12": nil} 712 | o := New(&m) 713 | assert.Nil(t, o.SetByKey("17", 71)) 714 | val, found := m["17"] 715 | assert.True(t, found) 716 | assert.Equal(t, 71, val) 717 | } 718 | 719 | func TestSetStringByIndex(t *testing.T) { 720 | s := "jkljkl" 721 | o := New(&s) 722 | assert.NotNil(t, o.SetByIndex(0, 'a')) 723 | } 724 | 725 | func TestWalk(t *testing.T) { 726 | t.Parallel() 727 | 728 | s := struct { 729 | String string 730 | PtrToString *string 731 | Map map[string]int 732 | PtrToMap *map[string]int 733 | Slice []string 734 | PtrToSlice *[]string 735 | Array [2]int 736 | PtrToArray *[2]int 737 | Struct struct{ Name string } 738 | }{} 739 | _ = s 740 | } 741 | 742 | func TestDereferencedIface(t *testing.T) { 743 | t.Parallel() 744 | 745 | ptrPerson := &Person{Name: "aaa"} 746 | for _, i := range []interface{}{ 747 | Person{Name: "aaa"}, 748 | &Person{Name: "aaa"}, 749 | &ptrPerson, 750 | } { 751 | fmt.Printf("type %T:\n", i) 752 | o := New(i) 753 | p := o.Dereferenced() 754 | _, is := p.(Person) 755 | assert.True(t, is, "found %T", p) 756 | } 757 | } 758 | -------------------------------------------------------------------------------- /reflector/tmp/testmodels.go: -------------------------------------------------------------------------------- 1 | package tmp 2 | 3 | // TestStruct ... 4 | type TestStruct struct { 5 | _ int `bu:"ba"` 6 | Exported string 7 | unexported int `aaa:"bbb" ccc:"ddd"` 8 | } 9 | 10 | func init() { 11 | var ts TestStruct 12 | _ = ts.unexported 13 | } 14 | -------------------------------------------------------------------------------- /reflector/utils.go: -------------------------------------------------------------------------------- 1 | package reflector 2 | 3 | import ( 4 | "fmt" 5 | "strconv" 6 | ) 7 | 8 | // ParseTag parses a golang struct tag into a map. 9 | func ParseTag(tag string) (map[string]string, error) { 10 | res := map[string]string{} 11 | 12 | // This code is copied/modified from: reflect/type.go: 13 | for tag != "" { 14 | // Skip leading space. 15 | i := 0 16 | for i < len(tag) && tag[i] == ' ' { 17 | i++ 18 | } 19 | tag = tag[i:] 20 | if tag == "" { 21 | break 22 | } 23 | 24 | // Scan to colon. A space, a quote or a control character is a syntax error. 25 | // Strictly speaking, control chars include the range [0x7f, 0x9f], not just 26 | // [0x00, 0x1f], but in practice, we ignore the multi-byte control characters 27 | // as it is simpler to inspect the tag's bytes than the tag's runes. 28 | i = 0 29 | for i < len(tag) && tag[i] > ' ' && tag[i] != ':' && tag[i] != '"' && tag[i] != 0x7f { 30 | i++ 31 | } 32 | if i == 0 || i+1 >= len(tag) || tag[i] != ':' || tag[i+1] != '"' { 33 | break 34 | } 35 | name := string(tag[:i]) 36 | tag = tag[i+1:] 37 | 38 | // Scan quoted string to find value. 39 | i = 1 40 | for i < len(tag) && tag[i] != '"' { 41 | if tag[i] == '\\' { 42 | i++ 43 | } 44 | i++ 45 | } 46 | if i >= len(tag) { 47 | break 48 | } 49 | qvalue := string(tag[:i+1]) 50 | tag = tag[i+1:] 51 | 52 | value, err := strconv.Unquote(qvalue) 53 | if err != nil { 54 | return nil, fmt.Errorf("Cannot unquote tag %s in %s: %s", name, tag, err.Error()) 55 | } 56 | res[name] = value 57 | } 58 | 59 | return res, nil 60 | } 61 | -------------------------------------------------------------------------------- /reflector/walk.go: -------------------------------------------------------------------------------- 1 | package reflector 2 | 3 | import "reflect" 4 | 5 | func Walk(i interface{}, f func(i2 interface{})) { 6 | ty := reflect.TypeOf(i) 7 | if ty.Kind() == reflect.Ptr { 8 | ty = ty.Elem() 9 | } 10 | } 11 | --------------------------------------------------------------------------------