├── go.mod ├── .github └── workflows │ └── test.yml ├── go.sum ├── LICENSE ├── README.md ├── lookup.go └── lookup_test.go /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/mcuadros/go-lookup 2 | 3 | go 1.14 4 | 5 | require ( 6 | github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e // indirect 7 | gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f 8 | ) 9 | -------------------------------------------------------------------------------- /.github/workflows/test.yml: -------------------------------------------------------------------------------- 1 | on: [push, pull_request] 2 | name: Test 3 | jobs: 4 | test: 5 | runs-on: ubuntu-latest 6 | steps: 7 | - name: Install Go 8 | uses: actions/setup-go@v1 9 | with: 10 | go-version: 1.14.x 11 | - name: Checkout code 12 | uses: actions/checkout@v2 13 | - name: Test 14 | run: go test ./... -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= 2 | github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= 3 | github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= 4 | github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWbfPhv4DMiApHyliiK5xCTNVSPiaAs= 5 | github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= 6 | gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f h1:BLraFXnmrev5lT+xlilqcH8XK9/i0At2xKjWk4p6zsU= 7 | gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 8 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 Máximo Cuadros 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. 22 | 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | go-lookup [![Build Status](https://img.shields.io/github/workflow/status/mcuadros/go-lookup/Test.svg)](https://github.com/mcuadros/go-lookup/actions) [![GoDoc](http://godoc.org/github.com/mcuadros/go-lookup?status.png)](https://pkg.go.dev/github.com/mcuadros/go-lookup) 2 | ============================== 3 | 4 | Small library on top of reflect for make lookups to Structs or Maps. Using a very simple DSL you can access to any property, key or value of any value of Go. 5 | 6 | Installation 7 | ------------ 8 | 9 | The recommended way to install go-lookup 10 | 11 | ``` 12 | go get github.com/mcuadros/go-lookup 13 | ``` 14 | 15 | Example 16 | ------- 17 | 18 | ```go 19 | type Cast struct { 20 | Actor, Role string 21 | } 22 | 23 | type Serie struct { 24 | Cast []Cast 25 | } 26 | 27 | series := map[string]Serie{ 28 | "A-Team": {Cast: []Cast{ 29 | {Actor: "George Peppard", Role: "Hannibal"}, 30 | {Actor: "Dwight Schultz", Role: "Murdock"}, 31 | {Actor: "Mr. T", Role: "Baracus"}, 32 | {Actor: "Dirk Benedict", Role: "Faceman"}, 33 | }}, 34 | } 35 | 36 | q := "A-Team.Cast.Role" 37 | value, _ := LookupString(series, q) 38 | fmt.Println(q, "->", value.Interface()) 39 | // A-Team.Cast.Role -> [Hannibal Murdock Baracus Faceman] 40 | 41 | q = "A-Team.Cast[0].Actor" 42 | value, _ = LookupString(series, q) 43 | fmt.Println(q, "->", value.Interface()) 44 | // A-Team.Cast[0].Actor -> George Peppard 45 | ``` 46 | 47 | ### Case-insensitive matching 48 | 49 | Use the `LookupI` and `LookupStringI` functions to do a case-insensitive match on struct field names and map keys. It will first look for an exact match; if that fails, it will fall back to a more expensive linear search over fields/keys. 50 | 51 | ```go 52 | type ExampleStruct struct { 53 | SoftwareUpdated bool 54 | } 55 | 56 | i := ExampleStruct{ 57 | SoftwareUpdated: true, 58 | } 59 | 60 | value, _ := LookupStringI(i, "softwareupdated") 61 | fmt.Println(value.Interface()) 62 | // Output: true 63 | ``` 64 | 65 | License 66 | ------- 67 | 68 | MIT, see [LICENSE](LICENSE) 69 | -------------------------------------------------------------------------------- /lookup.go: -------------------------------------------------------------------------------- 1 | /* 2 | Small library on top of reflect for make lookups to Structs or Maps. Using a 3 | very simple DSL you can access to any property, key or value of any value of Go. 4 | */ 5 | package lookup 6 | 7 | import ( 8 | "errors" 9 | "reflect" 10 | "strconv" 11 | "strings" 12 | ) 13 | 14 | const ( 15 | SplitToken = "." 16 | IndexCloseChar = "]" 17 | IndexOpenChar = "[" 18 | ) 19 | 20 | var ( 21 | ErrMalformedIndex = errors.New("malformed index key") 22 | ErrInvalidIndexUsage = errors.New("invalid index key usage") 23 | ErrKeyNotFound = errors.New("unable to find the key") 24 | ErrIndexOutOfBounds = errors.New("index out of bounds") 25 | ) 26 | 27 | // LookupString performs a lookup into a value, using a string. Same as `Lookup` 28 | // but using a string with the keys separated by `.` 29 | func LookupString(i interface{}, path string) (reflect.Value, error) { 30 | return Lookup(i, strings.Split(path, SplitToken)...) 31 | } 32 | 33 | // LookupStringI is the same as LookupString, but the path is not case 34 | // sensitive. 35 | func LookupStringI(i interface{}, path string) (reflect.Value, error) { 36 | return LookupI(i, strings.Split(path, SplitToken)...) 37 | } 38 | 39 | // Lookup performs a lookup into a value, using a path of keys. The key should 40 | // match with a Field or a MapIndex. For slice you can use the syntax key[index] 41 | // to access a specific index. If one key owns to a slice and an index is not 42 | // specificied the rest of the path will be apllied to evaley value of the 43 | // slice, and the value will be merged into a slice. 44 | func Lookup(i interface{}, path ...string) (reflect.Value, error) { 45 | return lookup(i, false, path...) 46 | } 47 | 48 | // LookupI is the same as Lookup, but the path keys are not case sensitive. 49 | func LookupI(i interface{}, path ...string) (reflect.Value, error) { 50 | return lookup(i, true, path...) 51 | } 52 | 53 | func lookup(i interface{}, caseInsensitive bool, path ...string) (reflect.Value, error) { 54 | value := reflect.ValueOf(i) 55 | var parent reflect.Value 56 | var err error 57 | 58 | for i, part := range path { 59 | parent = value 60 | 61 | value, err = getValueByName(value, part, caseInsensitive) 62 | if err == nil { 63 | continue 64 | } 65 | 66 | if !isAggregable(parent) { 67 | break 68 | } 69 | 70 | value, err = aggreateAggregableValue(parent, path[i:]) 71 | 72 | break 73 | } 74 | 75 | return value, err 76 | } 77 | 78 | func getValueByName(v reflect.Value, key string, caseInsensitive bool) (reflect.Value, error) { 79 | var value reflect.Value 80 | var index int = -1 81 | var err error 82 | 83 | prevKey := key 84 | key, index, err = parseIndex(key) 85 | if err != nil { 86 | return value, err 87 | } 88 | switch v.Kind() { 89 | case reflect.Ptr, reflect.Interface: 90 | return getValueByName(v.Elem(), prevKey, caseInsensitive) 91 | case reflect.Struct: 92 | value = v.FieldByName(key) 93 | 94 | if caseInsensitive && value.Kind() == reflect.Invalid { 95 | // We don't use FieldByNameFunc, since it returns zero value if the 96 | // match func matches multiple fields. Iterate here and return the 97 | // first matching field. 98 | for i := 0; i < v.NumField(); i++ { 99 | if strings.EqualFold(v.Type().Field(i).Name, key) { 100 | value = v.Field(i) 101 | break 102 | } 103 | } 104 | } 105 | 106 | case reflect.Map: 107 | kValue := reflect.Indirect(reflect.New(v.Type().Key())) 108 | kValue.SetString(key) 109 | value = v.MapIndex(kValue) 110 | if caseInsensitive && value.Kind() == reflect.Invalid { 111 | iter := v.MapRange() 112 | for iter.Next() { 113 | if strings.EqualFold(key, iter.Key().String()) { 114 | kValue.SetString(iter.Key().String()) 115 | value = v.MapIndex(kValue) 116 | break 117 | } 118 | } 119 | } 120 | } 121 | 122 | if !value.IsValid() { 123 | return reflect.Value{}, ErrKeyNotFound 124 | } 125 | 126 | if index != -1 { 127 | if value.Kind() == reflect.Ptr { 128 | value = value.Elem() 129 | } 130 | 131 | if value.Type().Kind() != reflect.Slice { 132 | return reflect.Value{}, ErrInvalidIndexUsage 133 | } 134 | 135 | if value.Len() <= index { 136 | return reflect.Value{}, ErrIndexOutOfBounds 137 | } 138 | 139 | value = value.Index(index) 140 | } 141 | 142 | if value.Kind() == reflect.Ptr || value.Kind() == reflect.Interface { 143 | value = value.Elem() 144 | } 145 | 146 | return value, nil 147 | } 148 | 149 | func aggreateAggregableValue(v reflect.Value, path []string) (reflect.Value, error) { 150 | values := make([]reflect.Value, 0) 151 | 152 | l := v.Len() 153 | if l == 0 { 154 | ty, ok := lookupType(v.Type(), path...) 155 | if !ok { 156 | return reflect.Value{}, ErrKeyNotFound 157 | } 158 | return reflect.MakeSlice(reflect.SliceOf(ty), 0, 0), nil 159 | } 160 | 161 | index := indexFunction(v) 162 | for i := 0; i < l; i++ { 163 | value, err := Lookup(index(i).Interface(), path...) 164 | if err != nil { 165 | return reflect.Value{}, err 166 | } 167 | 168 | values = append(values, value) 169 | } 170 | 171 | return mergeValue(values), nil 172 | } 173 | 174 | func indexFunction(v reflect.Value) func(i int) reflect.Value { 175 | switch v.Kind() { 176 | case reflect.Slice: 177 | return v.Index 178 | case reflect.Map: 179 | keys := v.MapKeys() 180 | return func(i int) reflect.Value { 181 | return v.MapIndex(keys[i]) 182 | } 183 | default: 184 | panic("unsuported kind for index") 185 | } 186 | } 187 | 188 | func mergeValue(values []reflect.Value) reflect.Value { 189 | values = removeZeroValues(values) 190 | l := len(values) 191 | if l == 0 { 192 | return reflect.Value{} 193 | } 194 | 195 | sample := values[0] 196 | mergeable := isMergeable(sample) 197 | t := sample.Type() 198 | if mergeable { 199 | t = t.Elem() 200 | } 201 | 202 | value := reflect.MakeSlice(reflect.SliceOf(t), 0, 0) 203 | for i := 0; i < l; i++ { 204 | if !values[i].IsValid() { 205 | continue 206 | } 207 | 208 | if mergeable { 209 | value = reflect.AppendSlice(value, values[i]) 210 | } else { 211 | value = reflect.Append(value, values[i]) 212 | } 213 | } 214 | 215 | return value 216 | } 217 | 218 | func removeZeroValues(values []reflect.Value) []reflect.Value { 219 | l := len(values) 220 | 221 | var v []reflect.Value 222 | for i := 0; i < l; i++ { 223 | if values[i].IsValid() { 224 | v = append(v, values[i]) 225 | } 226 | } 227 | 228 | return v 229 | } 230 | 231 | func isAggregable(v reflect.Value) bool { 232 | k := v.Kind() 233 | 234 | return k == reflect.Map || k == reflect.Slice 235 | } 236 | 237 | func isMergeable(v reflect.Value) bool { 238 | return v.Kind() == reflect.Slice 239 | } 240 | 241 | func hasIndex(s string) bool { 242 | return strings.Contains(s, IndexOpenChar) 243 | } 244 | 245 | func parseIndex(s string) (string, int, error) { 246 | start := strings.Index(s, IndexOpenChar) 247 | end := strings.Index(s, IndexCloseChar) 248 | 249 | if start == -1 && end == -1 { 250 | return s, -1, nil 251 | } 252 | 253 | if (start != -1 && end == -1) || (start == -1 && end != -1) { 254 | return "", -1, ErrMalformedIndex 255 | } 256 | 257 | index, err := strconv.Atoi(s[start+1 : end]) 258 | if err != nil { 259 | return "", -1, ErrMalformedIndex 260 | } 261 | 262 | return s[:start], index, nil 263 | } 264 | 265 | func lookupType(ty reflect.Type, path ...string) (reflect.Type, bool) { 266 | if len(path) == 0 { 267 | return ty, true 268 | } 269 | 270 | switch ty.Kind() { 271 | case reflect.Slice, reflect.Array, reflect.Map: 272 | if hasIndex(path[0]) { 273 | return lookupType(ty.Elem(), path[1:]...) 274 | } 275 | // Aggregate. 276 | return lookupType(ty.Elem(), path...) 277 | case reflect.Ptr: 278 | return lookupType(ty.Elem(), path...) 279 | case reflect.Interface: 280 | // We can't know from here without a value. Let's just return this type. 281 | return ty, true 282 | case reflect.Struct: 283 | f, ok := ty.FieldByName(path[0]) 284 | if ok { 285 | return lookupType(f.Type, path[1:]...) 286 | } 287 | } 288 | return nil, false 289 | } 290 | -------------------------------------------------------------------------------- /lookup_test.go: -------------------------------------------------------------------------------- 1 | package lookup 2 | 3 | import ( 4 | "fmt" 5 | "reflect" 6 | "testing" 7 | 8 | . "gopkg.in/check.v1" 9 | ) 10 | 11 | // Hook up gocheck into the "go test" runner. 12 | func Test(t *testing.T) { TestingT(t) } 13 | 14 | type S struct{} 15 | 16 | var _ = Suite(&S{}) 17 | 18 | func (s *S) TestLookup_Map(c *C) { 19 | value, err := Lookup(map[string]int{"foo": 42}, "foo") 20 | c.Assert(err, IsNil) 21 | c.Assert(value.Int(), Equals, int64(42)) 22 | } 23 | 24 | func (s *S) TestLookup_Ptr(c *C) { 25 | value, err := Lookup(&structFixture, "String") 26 | c.Assert(err, IsNil) 27 | c.Assert(value.String(), Equals, "foo") 28 | } 29 | 30 | func (s *S) TestLookup_Interface(c *C) { 31 | value, err := Lookup(structFixture, "Interface") 32 | 33 | c.Assert(err, IsNil) 34 | c.Assert(value.String(), Equals, "foo") 35 | } 36 | 37 | func (s *S) TestLookup_StructBasic(c *C) { 38 | value, err := Lookup(structFixture, "String") 39 | c.Assert(err, IsNil) 40 | c.Assert(value.String(), Equals, "foo") 41 | } 42 | 43 | func (s *S) TestLookup_StructPlusMap(c *C) { 44 | value, err := Lookup(structFixture, "Map", "foo") 45 | c.Assert(err, IsNil) 46 | c.Assert(value.Int(), Equals, int64(42)) 47 | } 48 | 49 | func (s *S) TestLookup_MapNamed(c *C) { 50 | value, err := Lookup(mapFixtureNamed, "foo") 51 | c.Assert(err, IsNil) 52 | c.Assert(value.Int(), Equals, int64(42)) 53 | } 54 | 55 | func (s *S) TestLookup_NotFound(c *C) { 56 | _, err := Lookup(structFixture, "qux") 57 | c.Assert(err, Equals, ErrKeyNotFound) 58 | 59 | _, err = Lookup(mapFixture, "qux") 60 | c.Assert(err, Equals, ErrKeyNotFound) 61 | } 62 | 63 | func (s *S) TestAggregableLookup_StructIndex(c *C) { 64 | value, err := Lookup(structFixture, "StructSlice", "Map", "foo") 65 | 66 | c.Assert(err, IsNil) 67 | c.Assert(value.Interface(), DeepEquals, []int{42, 42}) 68 | } 69 | 70 | func (s *S) TestAggregableLookup_StructNestedMap(c *C) { 71 | value, err := Lookup(structFixture, "StructSlice[0]", "String") 72 | 73 | c.Assert(err, IsNil) 74 | c.Assert(value.Interface(), DeepEquals, "foo") 75 | } 76 | 77 | func (s *S) TestAggregableLookup_StructNested(c *C) { 78 | value, err := Lookup(structFixture, "StructSlice", "StructSlice", "String") 79 | 80 | c.Assert(err, IsNil) 81 | c.Assert(value.Interface(), DeepEquals, []string{"bar", "foo", "qux", "baz"}) 82 | } 83 | 84 | func (s *S) TestAggregableLookupString_Complex(c *C) { 85 | value, err := LookupString(structFixture, "StructSlice.StructSlice[0].String") 86 | c.Assert(err, IsNil) 87 | c.Assert(value.Interface(), DeepEquals, []string{"bar", "qux"}) 88 | 89 | value, err = LookupString(structFixture, "StructSlice[0].Map.foo") 90 | c.Assert(err, IsNil) 91 | c.Assert(value.Interface(), DeepEquals, 42) 92 | 93 | value, err = LookupString(mapComplexFixture, "map.bar") 94 | c.Assert(err, IsNil) 95 | c.Assert(value.Interface(), DeepEquals, 1) 96 | 97 | value, err = LookupString(mapComplexFixture, "list.baz") 98 | c.Assert(err, IsNil) 99 | c.Assert(value.Interface(), DeepEquals, []int{1, 2, 3}) 100 | 101 | value, err = LookupString(mapComplexFixture, "list") 102 | c.Assert(err, IsNil) 103 | c.Assert(value.Interface(), DeepEquals, []map[string]interface{}{ 104 | {"baz": 1}, 105 | {"baz": 2}, 106 | {"baz": 3}, 107 | }) 108 | 109 | } 110 | 111 | func (s *S) TestAggregableLookup_EmptySlice(c *C) { 112 | fixture := [][]MyStruct{{}} 113 | value, err := LookupString(fixture, "String") 114 | c.Assert(err, IsNil) 115 | c.Assert(value.Interface().([]string), DeepEquals, []string{}) 116 | } 117 | 118 | func (s *S) TestAggregableLookup_EmptyMap(c *C) { 119 | fixture := map[string]*MyStruct{} 120 | value, err := LookupString(fixture, "Map") 121 | c.Assert(err, IsNil) 122 | c.Assert(value.Interface().([]map[string]int), DeepEquals, []map[string]int{}) 123 | } 124 | 125 | func (s *S) TestMergeValue(c *C) { 126 | v := mergeValue([]reflect.Value{reflect.ValueOf("qux"), reflect.ValueOf("foo")}) 127 | c.Assert(v.Interface(), DeepEquals, []string{"qux", "foo"}) 128 | } 129 | 130 | func (s *S) TestMergeValueSlice(c *C) { 131 | v := mergeValue([]reflect.Value{ 132 | reflect.ValueOf([]string{"foo", "bar"}), 133 | reflect.ValueOf([]string{"qux", "baz"}), 134 | }) 135 | 136 | c.Assert(v.Interface(), DeepEquals, []string{"foo", "bar", "qux", "baz"}) 137 | } 138 | 139 | func (s *S) TestMergeValueZero(c *C) { 140 | v := mergeValue([]reflect.Value{{}, reflect.ValueOf("foo")}) 141 | c.Assert(v.Interface(), DeepEquals, []string{"foo"}) 142 | } 143 | 144 | func (s *S) TestParseIndex(c *C) { 145 | key, index, err := parseIndex("foo[42]") 146 | c.Assert(err, IsNil) 147 | c.Assert(key, Equals, "foo") 148 | c.Assert(index, Equals, 42) 149 | } 150 | 151 | func (s *S) TestParseIndexNooIndex(c *C) { 152 | key, index, err := parseIndex("foo") 153 | c.Assert(err, IsNil) 154 | c.Assert(key, Equals, "foo") 155 | c.Assert(index, Equals, -1) 156 | } 157 | 158 | func (s *S) TestParseIndexMalFormed(c *C) { 159 | key, index, err := parseIndex("foo[]") 160 | c.Assert(err, Equals, ErrMalformedIndex) 161 | c.Assert(key, Equals, "") 162 | c.Assert(index, Equals, -1) 163 | 164 | key, index, err = parseIndex("foo[42") 165 | c.Assert(err, Equals, ErrMalformedIndex) 166 | c.Assert(key, Equals, "") 167 | c.Assert(index, Equals, -1) 168 | 169 | key, index, err = parseIndex("foo42]") 170 | c.Assert(err, Equals, ErrMalformedIndex) 171 | c.Assert(key, Equals, "") 172 | c.Assert(index, Equals, -1) 173 | } 174 | 175 | func (s *S) TestLookup_CaseSensitive(c *C) { 176 | _, err := Lookup(structFixture, "STring") 177 | c.Assert(err, Equals, ErrKeyNotFound) 178 | } 179 | 180 | func (s *S) TestLookup_CaseInsensitive(c *C) { 181 | value, err := LookupI(structFixture, "STring") 182 | c.Assert(err, IsNil) 183 | c.Assert(value.String(), Equals, "foo") 184 | } 185 | 186 | func (s *S) TestLookup_CaseInsensitive_ExactMatch(c *C) { 187 | value, err := LookupI(caseFixtureStruct, "Testfield") 188 | c.Assert(err, IsNil) 189 | c.Assert(value.Int(), Equals, int64(2)) 190 | } 191 | 192 | func (s *S) TestLookup_CaseInsensitive_FirstMatch(c *C) { 193 | value, err := LookupI(caseFixtureStruct, "testfield") 194 | c.Assert(err, IsNil) 195 | c.Assert(value.Int(), Equals, int64(1)) 196 | } 197 | 198 | func (s *S) TestLookup_CaseInsensitiveExactMatch(c *C) { 199 | value, err := LookupI(structFixture, "STring") 200 | c.Assert(err, IsNil) 201 | c.Assert(value.String(), Equals, "foo") 202 | } 203 | 204 | func (s *S) TestLookup_Map_CaseSensitive(c *C) { 205 | _, err := Lookup(map[string]int{"Foo": 42}, "foo") 206 | c.Assert(err, Equals, ErrKeyNotFound) 207 | } 208 | 209 | func (s *S) TestLookup_Map_CaseInsensitive(c *C) { 210 | value, err := LookupI(map[string]int{"Foo": 42}, "foo") 211 | c.Assert(err, IsNil) 212 | c.Assert(value.Int(), Equals, int64(42)) 213 | } 214 | 215 | func (s *S) TestLookup_Map_CaseInsensitive_ExactMatch(c *C) { 216 | value, err := LookupI(caseFixtureMap, "Testkey") 217 | c.Assert(err, IsNil) 218 | c.Assert(value.Int(), Equals, int64(2)) 219 | } 220 | 221 | func (s *S) TestLookup_Map_CaseInsensitive_FirstMatch(c *C) { 222 | value, err := LookupI(caseFixtureMap, "testkey") 223 | c.Assert(err, IsNil) 224 | c.Assert(value.Int(), Equals, int64(1)) 225 | } 226 | 227 | func (s *S) TestLookup_ListPtr(c *C) { 228 | type Inner struct { 229 | Value string 230 | } 231 | 232 | type Outer struct { 233 | Values *[]Inner 234 | } 235 | 236 | values := []Inner{{Value: "first"}, {Value: "second"}} 237 | data := Outer{Values: &values} 238 | 239 | value, err := LookupStringI(data, "Values[0].Value") 240 | c.Assert(err, IsNil) 241 | c.Assert(value.String(), Equals, "first") 242 | } 243 | 244 | func (s *S) TestLookup_Ptr_Index(c *C) { 245 | ptr := &structFixture 246 | value, err := LookupString(ptr, "StructSlice[1].String") 247 | c.Assert(err, IsNil) 248 | c.Assert(value.String(), Equals, "qux") 249 | } 250 | 251 | func (s *S) TestLookup_IndexOutOfBounds(c *C) { 252 | _, err := LookupString(structFixture, "StructSlice[42].String") 253 | c.Assert(err, Equals, ErrIndexOutOfBounds) 254 | } 255 | 256 | func ExampleLookupString() { 257 | type Cast struct { 258 | Actor, Role string 259 | } 260 | 261 | type Serie struct { 262 | Cast []Cast 263 | } 264 | 265 | series := map[string]Serie{ 266 | "A-Team": {Cast: []Cast{ 267 | {Actor: "George Peppard", Role: "Hannibal"}, 268 | {Actor: "Dwight Schultz", Role: "Murdock"}, 269 | {Actor: "Mr. T", Role: "Baracus"}, 270 | {Actor: "Dirk Benedict", Role: "Faceman"}, 271 | }}, 272 | } 273 | 274 | q := "A-Team.Cast.Role" 275 | value, _ := LookupString(series, q) 276 | fmt.Println(q, "->", value.Interface()) 277 | 278 | q = "A-Team.Cast[0].Actor" 279 | value, _ = LookupString(series, q) 280 | fmt.Println(q, "->", value.Interface()) 281 | 282 | // Output: 283 | // A-Team.Cast.Role -> [Hannibal Murdock Baracus Faceman] 284 | // A-Team.Cast[0].Actor -> George Peppard 285 | } 286 | 287 | func ExampleLookup() { 288 | type ExampleStruct struct { 289 | Values struct { 290 | Foo int 291 | } 292 | } 293 | 294 | i := ExampleStruct{} 295 | i.Values.Foo = 10 296 | 297 | value, _ := Lookup(i, "Values", "Foo") 298 | fmt.Println(value.Interface()) 299 | // Output: 10 300 | } 301 | 302 | func ExampleLookupStringI() { 303 | type ExampleStruct struct { 304 | SoftwareUpdated bool 305 | } 306 | 307 | i := ExampleStruct{ 308 | SoftwareUpdated: true, 309 | } 310 | 311 | value, _ := LookupStringI(i, "softwareupdated") 312 | fmt.Println(value.Interface()) 313 | // Output: true 314 | } 315 | 316 | type MyStruct struct { 317 | String string 318 | Map map[string]int 319 | Nested *MyStruct 320 | StructSlice []*MyStruct 321 | Interface interface{} 322 | } 323 | 324 | type MyKey string 325 | 326 | var mapFixtureNamed = map[MyKey]int{"foo": 42} 327 | var mapFixture = map[string]int{"foo": 42} 328 | var structFixture = MyStruct{ 329 | String: "foo", 330 | Map: mapFixture, 331 | Interface: "foo", 332 | StructSlice: []*MyStruct{ 333 | {Map: mapFixture, String: "foo", StructSlice: []*MyStruct{{String: "bar"}, {String: "foo"}}}, 334 | {Map: mapFixture, String: "qux", StructSlice: []*MyStruct{{String: "qux"}, {String: "baz"}}}, 335 | }, 336 | } 337 | 338 | var mapComplexFixture = map[string]interface{}{ 339 | "map": map[string]interface{}{ 340 | "bar": 1, 341 | }, 342 | "list": []map[string]interface{}{ 343 | {"baz": 1}, 344 | {"baz": 2}, 345 | {"baz": 3}, 346 | }, 347 | } 348 | 349 | var caseFixtureStruct = struct { 350 | Foo int 351 | TestField int 352 | Testfield int 353 | testField int 354 | }{ 355 | 0, 1, 2, 3, 356 | } 357 | 358 | var caseFixtureMap = map[string]int{ 359 | "Foo": 0, 360 | "TestKey": 1, 361 | "Testkey": 2, 362 | "testKey": 3, 363 | } 364 | --------------------------------------------------------------------------------