├── .travis.yml ├── example_test.go ├── LICENSE ├── README.md ├── jsonpath_test.go └── jsonpath.go /.travis.yml: -------------------------------------------------------------------------------- 1 | language: go 2 | go: 3 | - "1.10" 4 | -------------------------------------------------------------------------------- /example_test.go: -------------------------------------------------------------------------------- 1 | package jsonpath_test 2 | 3 | import ( 4 | "encoding/json" 5 | "fmt" 6 | 7 | "github.com/yalp/jsonpath" 8 | ) 9 | 10 | func ExampleRead() { 11 | raw := []byte(`{"hello":"world"}`) 12 | 13 | var data interface{} 14 | json.Unmarshal(raw, &data) 15 | 16 | out, err := jsonpath.Read(data, "$.hello") 17 | if err != nil { 18 | panic(err) 19 | } 20 | 21 | fmt.Print(out) 22 | // Output: world 23 | } 24 | 25 | func ExamplePrepare() { 26 | raw := []byte(`{"hello":"world"}`) 27 | 28 | helloFilter, err := jsonpath.Prepare("$.hello") 29 | if err != nil { 30 | panic(err) 31 | } 32 | 33 | var data interface{} 34 | if err = json.Unmarshal(raw, &data); err != nil { 35 | panic(err) 36 | } 37 | 38 | out, err := helloFilter(data) 39 | if err != nil { 40 | panic(err) 41 | } 42 | 43 | fmt.Print(out) 44 | // Output: world 45 | } 46 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | BSD 3-Clause License 2 | 3 | Copyright (c) 2015, Marc Capdevielle 4 | All rights reserved. 5 | 6 | Redistribution and use in source and binary forms, with or without 7 | modification, are permitted provided that the following conditions are met: 8 | 9 | * Redistributions of source code must retain the above copyright notice, this 10 | list of conditions and the following disclaimer. 11 | 12 | * Redistributions in binary form must reproduce the above copyright notice, 13 | this list of conditions and the following disclaimer in the documentation 14 | and/or other materials provided with the distribution. 15 | 16 | * Neither the name of the copyright holder nor the names of its 17 | contributors may be used to endorse or promote products derived from 18 | this software without specific prior written permission. 19 | 20 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 21 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 22 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 23 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 24 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 25 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 26 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 27 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 28 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 29 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 30 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![Build Status](https://travis-ci.org/yalp/jsonpath.svg?branch=master)](https://travis-ci.org/yalp/jsonpath) 2 | 3 | This was mostly an experiment to learn go and test using closures to interpret a JSON path. 4 | You should use https://github.com/PaesslerAG/jsonpath instead. 5 | 6 | # jsonpath 7 | 8 | a (partial) implementation in [Go](http://golang.org) based on [Stefan Goener JSON Path](http://goessner.net/articles/JsonPath/) 9 | 10 | ## Limitations 11 | 12 | * No support for subexpressions : `$books[(@.length-1)]` 13 | * No support for filters : `$books[?(@.price > 10)]` 14 | * Strings in brackets must use double quotes : `$["bookstore"]` 15 | * Cannot operate on struct fields 16 | 17 | The third limitation comes from using the `text/scanner` package from the standard library. 18 | The last one could be overcome by using reflection. 19 | 20 | ## JsonPath quick intro 21 | 22 | All expressions start `$`. 23 | 24 | Examples (supported by the current implementation) : 25 | * `$` the current object (or array) 26 | * `$.books` access to the key of an object (or `$["books"]`with bracket syntax) 27 | * `$.books[1]` access to the index of an array 28 | * `$.books[1].authors[1].name` chaining of keys and index 29 | * `$["books"][1]["authors"][1]["name"]` the same with braket syntax 30 | * `$.books[0,1,3]` union on an array 31 | * `$["books", "songs", "movies"]` union on an object 32 | * `$books[1:3]` second and third items of an array 33 | * `$books[:-2:2]` every two items except the last two of an array 34 | * `$books[::-1]` all items in reversed order 35 | * `$..authors` all authors (recursive search) 36 | 37 | Checkout the [tests](jsonpath_test.go) for more examples. 38 | 39 | ## Install 40 | 41 | go get github.com/yalp/jsonpath 42 | 43 | ## Usage 44 | 45 | A jsonpath applies to any JSON decoded data using `interface{}` when decoded with [encoding/json](http://golang.org/pkg/encoding/json/) : 46 | 47 | var bookstore interface{} 48 | err := json.Unmarshal(data, &bookstore) 49 | authors, err := jsonpath.Read(bookstore, "$..authors") 50 | 51 | A jsonpath expression can be prepared to be reused multiple times : 52 | 53 | allAuthors, err = jsonpath.Prepare("$..authors") 54 | ... 55 | var bookstore interface{} 56 | err := json.Unmarshal(data, &bookstore) 57 | authors, err := allAuthors(bookstore) 58 | 59 | The type of the values returned by the `Read` method or `Prepare` functions depends on the jsonpath expression. 60 | -------------------------------------------------------------------------------- /jsonpath_test.go: -------------------------------------------------------------------------------- 1 | package jsonpath 2 | 3 | import ( 4 | "encoding/json" 5 | "reflect" 6 | "strings" 7 | "testing" 8 | ) 9 | 10 | var goessner = []byte(`{ 11 | "store": { 12 | "book": [ 13 | { 14 | "category": "reference", 15 | "author": "Nigel Rees", 16 | "title": "Sayings of the Century", 17 | "price": 8.95 18 | }, 19 | { 20 | "category": "fiction", 21 | "author": "Evelyn Waugh", 22 | "title": "Sword of Honour", 23 | "price": 12.99 24 | }, 25 | { 26 | "category": "fiction", 27 | "author": "Herman Melville", 28 | "title": "Moby Dick", 29 | "isbn": "0-553-21311-3", 30 | "price": 8.99 31 | }, 32 | { 33 | "category": "fiction", 34 | "author": "J. R. R. Tolkien", 35 | "title": "The Lord of the Rings", 36 | "isbn": "0-395-19395-8", 37 | "price": 22.99 38 | } 39 | ], 40 | "bicycle": { 41 | "color": "red", 42 | "price": 19.95 43 | } 44 | }, 45 | "expensive": 10 46 | }`) 47 | 48 | var sample = map[string]interface{}{ 49 | "A": []interface{}{ 50 | "string", 51 | 23.3, 52 | 3, 53 | true, 54 | false, 55 | nil, 56 | }, 57 | "B": "value", 58 | "C": 3.14, 59 | "D": map[string]interface{}{ 60 | "C": 3.1415, 61 | "V": []interface{}{ 62 | "string2a", 63 | "string2b", 64 | map[string]interface{}{ 65 | "C": 3.141592, 66 | }, 67 | }, 68 | }, 69 | "E": map[string]interface{}{ 70 | "A": []interface{}{"string3"}, 71 | "D": map[string]interface{}{ 72 | "V": map[string]interface{}{ 73 | "C": 3.14159265, 74 | }, 75 | }, 76 | }, 77 | "F": map[string]interface{}{ 78 | "V": []interface{}{ 79 | "string4a", 80 | "string4b", 81 | map[string]interface{}{ 82 | "CC": 3.1415926535, 83 | }, 84 | map[string]interface{}{ 85 | "CC": "hello", 86 | }, 87 | []interface{}{ 88 | "string5a", 89 | "string5b", 90 | }, 91 | []interface{}{ 92 | "string6a", 93 | "string6b", 94 | }, 95 | }, 96 | }, 97 | } 98 | 99 | func TestGossner(t *testing.T) { 100 | var data interface{} 101 | json.Unmarshal(goessner, &data) 102 | 103 | tests := map[string]interface{}{ 104 | "$.store.book[*].author": []interface{}{"Nigel Rees", "Evelyn Waugh", "Herman Melville", "J. R. R. Tolkien"}, 105 | "$..author": []interface{}{"Nigel Rees", "Evelyn Waugh", "Herman Melville", "J. R. R. Tolkien"}, 106 | } 107 | assert(t, data, tests) 108 | } 109 | 110 | func TestParsing(t *testing.T) { 111 | t.Run("pick", func(t *testing.T) { 112 | assert(t, sample, map[string]interface{}{ 113 | "$": sample, 114 | "$.A[0]": "string", 115 | `$["A"][0]`: "string", 116 | "$.A": []interface{}{"string", 23.3, 3, true, false, nil}, 117 | "$.A[*]": []interface{}{"string", 23.3, 3, true, false, nil}, 118 | "$.A.*": []interface{}{"string", 23.3, 3, true, false, nil}, 119 | "$.A.*.a": []interface{}{}, 120 | }) 121 | }) 122 | 123 | t.Run("slice", func(t *testing.T) { 124 | assert(t, sample, map[string]interface{}{ 125 | "$.A[1,4,2]": []interface{}{23.3, false, 3}, 126 | `$["B","C"]`: []interface{}{"value", 3.14}, 127 | `$["C","B"]`: []interface{}{3.14, "value"}, 128 | "$.A[1:4]": []interface{}{23.3, 3, true}, 129 | "$.A[::2]": []interface{}{"string", 3, false}, 130 | "$.A[-2:]": []interface{}{false, nil}, 131 | "$.A[:-1]": []interface{}{"string", 23.3, 3, true, false}, 132 | "$.A[::-1]": []interface{}{nil, false, true, 3, 23.3, "string"}, 133 | "$.F.V[4:5][0,1]": []interface{}{"string5a", "string5b"}, 134 | "$.F.V[4:6][1]": []interface{}{"string5b", "string6b"}, 135 | "$.F.V[4:6][0,1]": []interface{}{"string5a", "string5b", "string6a", "string6b"}, 136 | "$.F.V[4,5][0:2]": []interface{}{"string5a", "string5b", "string6a", "string6b"}, 137 | "$.F.V[4:6]": []interface{}{ 138 | []interface{}{ 139 | "string5a", 140 | "string5b", 141 | }, 142 | []interface{}{ 143 | "string6a", 144 | "string6b", 145 | }, 146 | }, 147 | }) 148 | }) 149 | 150 | t.Run("quote", func(t *testing.T) { 151 | assert(t, sample, map[string]interface{}{ 152 | `$[A][0]`: "string", 153 | `$["A"][0]`: "string", 154 | `$[B,C]`: []interface{}{"value", 3.14}, 155 | `$["B","C"]`: []interface{}{"value", 3.14}, 156 | }) 157 | }) 158 | 159 | t.Run("search", func(t *testing.T) { 160 | assert(t, sample, map[string]interface{}{ 161 | "$..C": []interface{}{3.14, 3.1415, 3.141592, 3.14159265}, 162 | `$..["C"]`: []interface{}{3.14, 3.1415, 3.141592, 3.14159265}, 163 | "$.D.V..C": []interface{}{3.141592}, 164 | "$.D.V.*.C": []interface{}{3.141592}, 165 | "$.D.V..*.C": []interface{}{3.141592}, 166 | "$.D.*..C": []interface{}{3.141592}, 167 | "$.*.V..C": []interface{}{3.141592}, 168 | "$.*.D.V.C": []interface{}{3.14159265}, 169 | "$.*.D..C": []interface{}{3.14159265}, 170 | "$.*.D.V..*": []interface{}{3.14159265}, 171 | "$..D..V..C": []interface{}{3.141592, 3.14159265}, 172 | "$.*.*.*.C": []interface{}{3.141592, 3.14159265}, 173 | "$..V..C": []interface{}{3.141592, 3.14159265}, 174 | "$.D.V..*": []interface{}{ 175 | "string2a", 176 | "string2b", 177 | map[string]interface{}{ 178 | "C": 3.141592, 179 | }, 180 | 3.141592, 181 | }, 182 | "$..A": []interface{}{ 183 | []interface{}{"string", 23.3, 3, true, false, nil}, 184 | []interface{}{"string3"}, 185 | }, 186 | "$..A..*": []interface{}{"string", 23.3, 3, true, false, nil, "string3"}, 187 | "$.A..*": []interface{}{"string", 23.3, 3, true, false, nil}, 188 | "$.A.*": []interface{}{"string", 23.3, 3, true, false, nil}, 189 | "$..A[0,1]": []interface{}{"string", 23.3}, 190 | "$..A[0]": []interface{}{"string", "string3"}, 191 | "$.*.V[0]": []interface{}{"string2a", "string4a"}, 192 | "$.*.V[1]": []interface{}{"string2b", "string4b"}, 193 | "$.*.V[0,1]": []interface{}{"string2a", "string2b", "string4a", "string4b"}, 194 | "$.*.V[0:2]": []interface{}{"string2a", "string2b", "string4a", "string4b"}, 195 | "$.*.V[2].C": []interface{}{3.141592}, 196 | "$..V[2].C": []interface{}{3.141592}, 197 | "$..V[*].C": []interface{}{3.141592}, 198 | "$.*.V[2].*": []interface{}{3.141592, 3.1415926535}, 199 | "$.*.V[2:3].*": []interface{}{3.141592, 3.1415926535}, 200 | "$.*.V[2:4].*": []interface{}{3.141592, 3.1415926535, "hello"}, 201 | "$..V[2,3].CC": []interface{}{3.1415926535, "hello"}, 202 | "$..V[2:4].CC": []interface{}{3.1415926535, "hello"}, 203 | "$..V[*].*": []interface{}{ 204 | 3.141592, 205 | 3.1415926535, 206 | "hello", 207 | "string5a", 208 | "string5b", 209 | "string6a", 210 | "string6b", 211 | }, 212 | "$..[0]": []interface{}{ 213 | "string", 214 | "string2a", 215 | "string3", 216 | "string4a", 217 | "string5a", 218 | "string6a", 219 | }, 220 | "$..ZZ": []interface{}{}, 221 | }) 222 | }) 223 | } 224 | 225 | func TestErrors(t *testing.T) { 226 | tests := map[string]string{ 227 | ".A": "path must start with a '$'", 228 | "$.": "expected JSON child identifier after '.'", 229 | "$.1": "unexpected token .1", 230 | "$.A[]": "expected at least one key, index or expression", 231 | `$["]`: "bad string invalid syntax", 232 | `$[A][0`: "unexpected end of path", 233 | "$.ZZZ": "child 'ZZZ' not found in JSON object", 234 | "$.A*]": "unexpected token *", 235 | "$.*V": "unexpected token V", 236 | "$[B,C": "unexpected end of path", 237 | "$.A[1,4.2]": "unexpected token '.'", 238 | "$[C:B]": "expected JSON array", 239 | "$.A[1:4:0:0]": "bad range syntax [start:end:step]", 240 | "$.A[:,]": "unexpected token ','", 241 | "$..": "cannot end with a scan '..'", 242 | "$..1": "unexpected token '1' after deep search '..'", 243 | } 244 | assertError(t, sample, tests) 245 | } 246 | 247 | func assert(t *testing.T, json interface{}, tests map[string]interface{}) { 248 | for path, expected := range tests { 249 | actual, err := Read(json, path) 250 | if err != nil { 251 | t.Error("failed:", path, err) 252 | } else if !reflect.DeepEqual(actual, expected) { 253 | t.Errorf("failed: mismatch for %s\nexpected: %+v\nactual: %+v", path, expected, actual) 254 | } 255 | } 256 | } 257 | 258 | func assertError(t *testing.T, json interface{}, tests map[string]string) { 259 | for path, expectedError := range tests { 260 | _, err := Read(json, path) 261 | if err == nil { 262 | t.Error("path", path, "should fail with", expectedError) 263 | } else if !strings.Contains(err.Error(), expectedError) { 264 | t.Error("path", path, "shoud fail with ", expectedError, "but failed with:", err) 265 | } 266 | } 267 | } 268 | -------------------------------------------------------------------------------- /jsonpath.go: -------------------------------------------------------------------------------- 1 | // Package jsonpath implements Stefan Goener's JSONPath http://goessner.net/articles/JsonPath/ 2 | // 3 | // A jsonpath applies to any JSON decoded data using interface{} when 4 | // decoded with encoding/json (http://golang.org/pkg/encoding/json/) : 5 | // 6 | // var bookstore interface{} 7 | // err := json.Unmarshal(data, &bookstore) 8 | // authors, err := jsonpath.Read(bookstore, "$..authors") 9 | // 10 | // A jsonpath expression can be prepared to be reused multiple times : 11 | // 12 | // allAuthors, err := jsonpath.Prepare("$..authors") 13 | // ... 14 | // var bookstore interface{} 15 | // err = json.Unmarshal(data, &bookstore) 16 | // authors, err := allAuthors(bookstore) 17 | // 18 | // The type of the values returned by the `Read` method or `Prepare` 19 | // functions depends on the jsonpath expression. 20 | // 21 | // Limitations 22 | // 23 | // No support for subexpressions and filters. 24 | // Strings in brackets must use double quotes. 25 | // It cannot operate on JSON decoded struct fields. 26 | // 27 | package jsonpath 28 | 29 | import ( 30 | "errors" 31 | "fmt" 32 | "sort" 33 | "strconv" 34 | "strings" 35 | "text/scanner" 36 | ) 37 | 38 | // Read a path from a decoded JSON array or object ([]interface{} or map[string]interface{}) 39 | // and returns the corresponding value or an error. 40 | // 41 | // The returned value type depends on the requested path and the JSON value. 42 | func Read(value interface{}, path string) (interface{}, error) { 43 | filter, err := Prepare(path) 44 | if err != nil { 45 | return nil, err 46 | } 47 | return filter(value) 48 | } 49 | 50 | // Prepare will parse the path and return a filter function that can then be applied to decoded JSON values. 51 | func Prepare(path string) (FilterFunc, error) { 52 | p := newScanner(path) 53 | if err := p.parse(); err != nil { 54 | return nil, err 55 | } 56 | return p.prepareFilterFunc(), nil 57 | } 58 | 59 | // FilterFunc applies a prepared json path to a JSON decoded value 60 | type FilterFunc func(value interface{}) (interface{}, error) 61 | 62 | // short variables 63 | // p: the parser context 64 | // r: root node => @ 65 | // c: current node => $ 66 | // a: the list of actions to apply next 67 | // v: value 68 | 69 | // actionFunc applies a transformation to current value (possibility using root) 70 | // then applies the next action from actions (using next()) to the output of the transformation 71 | type actionFunc func(r, c interface{}, a actions) (interface{}, error) 72 | 73 | // a list of action functions to apply one after the other 74 | type actions []actionFunc 75 | 76 | // next applies the next action function 77 | func (a actions) next(r, c interface{}) (interface{}, error) { 78 | return a[0](r, c, a[1:]) 79 | } 80 | 81 | // call applies the next action function without taking it out 82 | func (a actions) call(r, c interface{}) (interface{}, error) { 83 | return a[0](r, c, a) 84 | } 85 | 86 | type exprFunc func(r, c interface{}) (interface{}, error) 87 | 88 | type searchResults []interface{} 89 | 90 | func (sr searchResults) append(v interface{}) searchResults { 91 | if vsr, ok := v.(searchResults); ok { 92 | return append(sr, vsr...) 93 | } 94 | return append(sr, v) 95 | } 96 | 97 | type parser struct { 98 | scanner scanner.Scanner 99 | path string 100 | actions actions 101 | } 102 | 103 | func (p *parser) prepareFilterFunc() FilterFunc { 104 | actions := p.actions 105 | return func(value interface{}) (interface{}, error) { 106 | result, err := actions.next(value, value) 107 | if err == nil { 108 | if sr, ok := result.(searchResults); ok { 109 | result = ([]interface{})(sr) 110 | } 111 | } 112 | return result, err 113 | } 114 | } 115 | 116 | func newScanner(path string) *parser { 117 | return &parser{path: path} 118 | } 119 | 120 | func (p *parser) scan() rune { 121 | return p.scanner.Scan() 122 | } 123 | 124 | func (p *parser) text() string { 125 | return p.scanner.TokenText() 126 | } 127 | 128 | func (p *parser) column() int { 129 | return p.scanner.Position.Column 130 | } 131 | 132 | func (p *parser) peek() rune { 133 | return p.scanner.Peek() 134 | } 135 | 136 | func (p *parser) add(action actionFunc) { 137 | p.actions = append(p.actions, action) 138 | } 139 | 140 | func (p *parser) parse() error { 141 | p.scanner.Init(strings.NewReader(p.path)) 142 | if p.scan() != '$' { 143 | return errors.New("path must start with a '$'") 144 | } 145 | return p.parsePath() 146 | } 147 | 148 | func (p *parser) parsePath() (err error) { 149 | for err == nil { 150 | switch p.scan() { 151 | case '.': 152 | p.scanner.Mode = scanner.ScanIdents 153 | switch p.scan() { 154 | case scanner.Ident: 155 | err = p.parseObjAccess() 156 | case '*': 157 | err = p.prepareWildcard() 158 | case '.': 159 | err = p.parseDeep() 160 | default: 161 | err = fmt.Errorf("expected JSON child identifier after '.' at %d", p.column()) 162 | } 163 | case '[': 164 | err = p.parseBracket() 165 | case scanner.EOF: 166 | // the end, add a last func that just return current node 167 | p.add(func(r, c interface{}, a actions) (interface{}, error) { return c, nil }) 168 | return nil 169 | default: 170 | err = fmt.Errorf("unexpected token %s at %d", p.text(), p.column()) 171 | } 172 | } 173 | return 174 | } 175 | 176 | func (p *parser) parseObjAccess() error { 177 | ident := p.text() 178 | column := p.scanner.Position.Column 179 | p.add(func(r, c interface{}, a actions) (interface{}, error) { 180 | obj, ok := c.(map[string]interface{}) 181 | if !ok { 182 | return nil, fmt.Errorf("expected JSON object to access child '%s' at %d", ident, column) 183 | } 184 | if c, ok = obj[ident]; !ok { 185 | return nil, fmt.Errorf("child '%s' not found in JSON object at %d", ident, column) 186 | } 187 | return a.next(r, c) 188 | }) 189 | return nil 190 | } 191 | 192 | func (p *parser) prepareWildcard() error { 193 | p.add(func(r, c interface{}, a actions) (interface{}, error) { 194 | values := searchResults{} 195 | if obj, ok := c.(map[string]interface{}); ok { 196 | for _, v := range valuesSortedByKey(obj) { 197 | v, err := a.next(r, v) 198 | if err != nil { 199 | continue 200 | } 201 | values = values.append(v) 202 | } 203 | } else if array, ok := c.([]interface{}); ok { 204 | for _, v := range array { 205 | v, err := a.next(r, v) 206 | if err != nil { 207 | continue 208 | } 209 | values = values.append(v) 210 | } 211 | } 212 | return values, nil 213 | }) 214 | return nil 215 | } 216 | 217 | func (p *parser) parseDeep() (err error) { 218 | p.scanner.Mode = scanner.ScanIdents 219 | switch p.scan() { 220 | case scanner.Ident: 221 | p.add(func(r, c interface{}, a actions) (interface{}, error) { 222 | return recSearchParent(r, c, a, searchResults{}), nil 223 | }) 224 | return p.parseObjAccess() 225 | case '[': 226 | p.add(func(r, c interface{}, a actions) (interface{}, error) { 227 | return recSearchParent(r, c, a, searchResults{}), nil 228 | }) 229 | return p.parseBracket() 230 | case '*': 231 | p.add(func(r, c interface{}, a actions) (interface{}, error) { 232 | return recSearchChildren(r, c, a, searchResults{}), nil 233 | }) 234 | p.add(func(r, c interface{}, a actions) (interface{}, error) { 235 | return a.next(r, c) 236 | }) 237 | return nil 238 | case scanner.EOF: 239 | return fmt.Errorf("cannot end with a scan '..' at %d", p.column()) 240 | default: 241 | return fmt.Errorf("unexpected token '%s' after deep search '..' at %d", 242 | p.text(), p.column()) 243 | } 244 | } 245 | 246 | // bracket contains filter, wildcard or array access 247 | func (p *parser) parseBracket() error { 248 | if p.peek() == '?' { 249 | return p.parseFilter() 250 | } else if p.peek() == '*' { 251 | p.scan() // eat * 252 | if p.scan() != ']' { 253 | return fmt.Errorf("expected closing bracket after [* at %d", p.column()) 254 | } 255 | return p.prepareWildcard() 256 | } 257 | return p.parseArray() 258 | } 259 | 260 | // array contains either a union [,,,], a slice [::] or a single element. 261 | // Each element can be an int, a string or an expression. 262 | // TODO optimize map/array access (by detecting the type of indexes) 263 | func (p *parser) parseArray() error { 264 | var indexes []interface{} // string, int or exprFunc 265 | var mode string // slice or union 266 | p.scanner.Mode = scanner.ScanIdents | scanner.ScanStrings | scanner.ScanInts 267 | parse: 268 | for { 269 | // parse value 270 | switch p.scan() { 271 | case scanner.Int: 272 | index, err := strconv.Atoi(p.text()) 273 | if err != nil { 274 | return fmt.Errorf("%s at %d", err.Error(), p.column()) 275 | } 276 | indexes = append(indexes, index) 277 | case '-': 278 | if p.scan() != scanner.Int { 279 | return fmt.Errorf("expect an int after the minus '-' sign at %d", p.column()) 280 | } 281 | index, err := strconv.Atoi(p.text()) 282 | if err != nil { 283 | return fmt.Errorf("%s at %d", err.Error(), p.column()) 284 | } 285 | indexes = append(indexes, -index) 286 | case scanner.Ident: 287 | indexes = append(indexes, p.text()) 288 | case scanner.String: 289 | s, err := strconv.Unquote(p.text()) 290 | if err != nil { 291 | return fmt.Errorf("bad string %s at %d", err, p.column()) 292 | } 293 | indexes = append(indexes, s) 294 | case '(': 295 | filter, err := p.parseExpression() 296 | if err != nil { 297 | return err 298 | } 299 | indexes = append(indexes, filter) 300 | case ':': // when slice value is omitted 301 | if mode == "" { 302 | mode = "slice" 303 | indexes = append(indexes, 0) 304 | } else if mode == "slice" { 305 | indexes = append(indexes, 0) 306 | } else { 307 | return fmt.Errorf("unexpected ':' after %s at %d", mode, p.column()) 308 | } 309 | continue // skip separator parsing, it's done 310 | case ']': // when slice value is omitted 311 | if mode == "slice" { 312 | indexes = append(indexes, 0) 313 | } else if len(indexes) == 0 { 314 | return fmt.Errorf("expected at least one key, index or expression at %d", p.column()) 315 | } 316 | break parse 317 | case scanner.EOF: 318 | return fmt.Errorf("unexpected end of path at %d", p.column()) 319 | default: 320 | return fmt.Errorf("unexpected token '%s' at %d", p.text(), p.column()) 321 | } 322 | // parse separator 323 | switch p.scan() { 324 | case ',': 325 | if mode == "" { 326 | mode = "union" 327 | } else if mode != "union" { 328 | return fmt.Errorf("unexpeted ',' in %s at %d", mode, p.column()) 329 | } 330 | case ':': 331 | if mode == "" { 332 | mode = "slice" 333 | } else if mode != "slice" { 334 | return fmt.Errorf("unexpected ':' in %s at %d", mode, p.column()) 335 | } 336 | case ']': 337 | break parse 338 | case scanner.EOF: 339 | return fmt.Errorf("unexpected end of path at %d", p.column()) 340 | default: 341 | return fmt.Errorf("unexpected token '%s' at %d", p.text(), p.column()) 342 | } 343 | } 344 | if mode == "slice" { 345 | if len(indexes) > 3 { 346 | return fmt.Errorf("bad range syntax [start:end:step] at %d", p.column()) 347 | } 348 | p.add(prepareSlice(indexes, p.column())) 349 | } else if len(indexes) == 1 { 350 | p.add(prepareIndex(indexes[0], p.column())) 351 | } else { 352 | p.add(prepareUnion(indexes, p.column())) 353 | } 354 | return nil 355 | } 356 | 357 | func (p *parser) parseFilter() error { 358 | return errors.New("Filters are not (yet) implemented") 359 | } 360 | 361 | func (p *parser) parseExpression() (exprFunc, error) { 362 | return nil, errors.New("Expression are not (yet) implemented") 363 | } 364 | 365 | func recSearchParent(r, c interface{}, a actions, acc searchResults) searchResults { 366 | if v, err := a.next(r, c); err == nil { 367 | acc = acc.append(v) 368 | } 369 | return recSearchChildren(r, c, a, acc) 370 | } 371 | 372 | func recSearchChildren(r, c interface{}, a actions, acc searchResults) searchResults { 373 | if obj, ok := c.(map[string]interface{}); ok { 374 | for _, c := range valuesSortedByKey(obj) { 375 | acc = recSearchParent(r, c, a, acc) 376 | } 377 | } else if array, ok := c.([]interface{}); ok { 378 | for _, c := range array { 379 | acc = recSearchParent(r, c, a, acc) 380 | } 381 | } 382 | return acc 383 | } 384 | 385 | func prepareIndex(index interface{}, column int) actionFunc { 386 | return func(r, c interface{}, a actions) (interface{}, error) { 387 | if obj, ok := c.(map[string]interface{}); ok { 388 | key, err := indexAsString(index, r, c) 389 | if err != nil { 390 | return nil, err 391 | } 392 | if c, ok = obj[key]; !ok { 393 | return nil, fmt.Errorf("no key '%s' for object at %d", key, column) 394 | } 395 | return a.next(r, c) 396 | } else if array, ok := c.([]interface{}); ok { 397 | index, err := indexAsInt(index, r, c) 398 | if err != nil { 399 | return nil, err 400 | } 401 | if index < 0 || index >= len(array) { 402 | return nil, fmt.Errorf("out of bound array access at %d", column) 403 | } 404 | return a.next(r, array[index]) 405 | } 406 | return nil, fmt.Errorf("expected array or object at %d", column) 407 | } 408 | } 409 | 410 | func prepareSlice(indexes []interface{}, column int) actionFunc { 411 | return func(r, c interface{}, a actions) (interface{}, error) { 412 | array, ok := c.([]interface{}) 413 | if !ok { 414 | return nil, fmt.Errorf("expected JSON array at %d", column) 415 | } 416 | var err error 417 | var start, end, step int 418 | if start, err = indexAsInt(indexes[0], r, c); err != nil { 419 | return nil, err 420 | } 421 | if end, err = indexAsInt(indexes[1], r, c); err != nil { 422 | return nil, err 423 | } 424 | if len(indexes) > 2 { 425 | if step, err = indexAsInt(indexes[2], r, c); err != nil { 426 | return nil, err 427 | } 428 | } 429 | max := len(array) 430 | start = negmax(start, max) 431 | if end == 0 { 432 | end = max 433 | } else { 434 | end = negmax(end, max) 435 | } 436 | if start > end { 437 | return nil, fmt.Errorf("cannot start range at %d and end at %d", start, end) 438 | } 439 | if step == 0 { 440 | step = 1 441 | } 442 | var values searchResults 443 | if step > 0 { 444 | for i := start; i < end; i += step { 445 | v, err := a.next(r, array[i]) 446 | if err != nil { 447 | continue 448 | } 449 | values = values.append(v) 450 | } 451 | } else { // reverse order on negative step 452 | for i := end - 1; i >= start; i += step { 453 | v, err := a.next(r, array[i]) 454 | if err != nil { 455 | continue 456 | } 457 | values = values.append(v) 458 | } 459 | } 460 | return values, nil 461 | } 462 | } 463 | 464 | func prepareUnion(indexes []interface{}, column int) actionFunc { 465 | return func(r, c interface{}, a actions) (interface{}, error) { 466 | if obj, ok := c.(map[string]interface{}); ok { 467 | var values searchResults 468 | for _, index := range indexes { 469 | key, err := indexAsString(index, r, c) 470 | if err != nil { 471 | return nil, err 472 | } 473 | if c, ok = obj[key]; !ok { 474 | return nil, fmt.Errorf("no key '%s' for object at %d", key, column) 475 | } 476 | if c, err = a.next(r, c); err != nil { 477 | return nil, err 478 | } 479 | values = values.append(c) 480 | } 481 | return values, nil 482 | } else if array, ok := c.([]interface{}); ok { 483 | var values searchResults 484 | for _, index := range indexes { 485 | index, err := indexAsInt(index, r, c) 486 | if err != nil { 487 | return nil, err 488 | } 489 | if index < 0 || index >= len(array) { 490 | return nil, fmt.Errorf("out of bound array access at %d", column) 491 | } 492 | if c, err = a.next(r, array[index]); err != nil { 493 | return nil, err 494 | } 495 | values = values.append(c) 496 | } 497 | return values, nil 498 | } 499 | return nil, fmt.Errorf("expected array or object at %d", column) 500 | } 501 | } 502 | 503 | func negmax(n, max int) int { 504 | if n < 0 { 505 | n = max + n 506 | if n < 0 { 507 | n = 0 508 | } 509 | } else if n > max { 510 | return max 511 | } 512 | return n 513 | } 514 | 515 | func indexAsInt(index, r, c interface{}) (int, error) { 516 | switch i := index.(type) { 517 | case int: 518 | return i, nil 519 | case exprFunc: 520 | index, err := i(r, c) 521 | if err != nil { 522 | return 0, err 523 | } 524 | switch i := index.(type) { 525 | case int: 526 | return i, nil 527 | default: 528 | return 0, fmt.Errorf("expected expression to return an index for array access") 529 | } 530 | default: 531 | return 0, fmt.Errorf("expected index value (integer or expression returning an integer) for array access") 532 | } 533 | } 534 | 535 | func indexAsString(key, r, c interface{}) (string, error) { 536 | switch s := key.(type) { 537 | case string: 538 | return s, nil 539 | case exprFunc: 540 | key, err := s(r, c) 541 | if err != nil { 542 | return "", err 543 | } 544 | switch s := key.(type) { 545 | case string: 546 | return s, nil 547 | default: 548 | return "", fmt.Errorf("expected expression to return a key for object access") 549 | } 550 | default: 551 | return "", fmt.Errorf("expected key value (string or expression returning a string) for object access") 552 | } 553 | } 554 | 555 | func valuesSortedByKey(m map[string]interface{}) []interface{} { 556 | if len(m) == 0 { 557 | return nil 558 | } 559 | keys := make([]string, 0, len(m)) 560 | for k := range m { 561 | keys = append(keys, k) 562 | } 563 | sort.Strings(keys) 564 | values := make([]interface{}, 0, len(m)) 565 | for _, k := range keys { 566 | values = append(values, m[k]) 567 | } 568 | return values 569 | } 570 | --------------------------------------------------------------------------------