├── .github └── workflows │ └── go.yml ├── .gitignore ├── LICENSE ├── Makefile ├── README.md ├── doc.go ├── drain.go ├── drain_test.go ├── error.go ├── frame.go ├── go.mod ├── go.sum ├── package_test.go ├── pointer.go ├── pointer_test.go ├── proxy.go ├── proxy_test.go ├── set.go ├── set_test.go ├── staticcheck.conf ├── type.go ├── value.go └── value_test.go /.github/workflows/go.yml: -------------------------------------------------------------------------------- 1 | name: Go 2 | 3 | on: [push, pull_request] 4 | 5 | jobs: 6 | 7 | build: 8 | name: Build 9 | runs-on: ${{ matrix.os }} 10 | 11 | strategy: 12 | matrix: 13 | os: [ ubuntu-latest, macos-latest, windows-latest ] 14 | steps: 15 | 16 | - uses: actions/setup-go@v2 17 | with: 18 | go-version: 1.x 19 | 20 | - name: Add $GOPATH/bin to $PATH 21 | run: | 22 | echo "::add-path::$(go env GOPATH)/bin" 23 | 24 | - uses: actions/checkout@v2 25 | 26 | - run: go test 27 | 28 | - run: go build 29 | 30 | # based on: github.com/koron-go/_skeleton/.github/workflows/go.yml 31 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /tags 2 | /tmp/ 3 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 MURAOKA Taro 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 13 | all 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 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | .PHONY: build 2 | build: 3 | go build -gcflags '-e' 4 | 5 | .PHONY: test 6 | test: 7 | go test ./... 8 | 9 | .PHONY: tags 10 | tags: 11 | gotags -f tags -R . 12 | 13 | .PHONY: cover 14 | cover: 15 | mkdir -p tmp 16 | go test -coverprofile tmp/_cover.out ./... 17 | go tool cover -html tmp/_cover.out -o tmp/cover.html 18 | 19 | .PHONY: checkall 20 | checkall: vet lint staticcheck 21 | 22 | .PHONY: vet 23 | vet: 24 | go vet ./... 25 | 26 | .PHONY: lint 27 | lint: 28 | golint ./... 29 | 30 | .PHONY: staticcheck 31 | staticcheck: 32 | staticcheck ./... 33 | 34 | .PHONY: clean 35 | clean: 36 | go clean 37 | rm -f tags 38 | rm -f tmp/_cover.out tmp/cover.html 39 | 40 | # based on: github.com/koron-go/_skeleton/Makefile 41 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # dProxy - document proxy 2 | 3 | [![GoDoc](https://godoc.org/github.com/koron/go-dproxy?status.svg)](https://godoc.org/github.com/koron/go-dproxy) 4 | [![Actions/Go](https://github.com/koron/go-dproxy/workflows/Go/badge.svg)](https://github.com/koron/go-dproxy/actions?query=workflow%3AGo) 5 | [![Go Report Card](https://goreportcard.com/badge/github.com/koron/go-dproxy)](https://goreportcard.com/report/github.com/koron/go-dproxy) 6 | 7 | dProxy is a proxy to access `interface{}` (document) by simple query. 8 | It is intented to be used with `json.Unmarshal()` or `json.NewDecorder()`. 9 | 10 | See codes for overview. 11 | 12 | ```go 13 | import ( 14 | "encoding/json" 15 | 16 | "github.com/koron/go-dproxy" 17 | ) 18 | 19 | var v interface{} 20 | json.Unmarshal([]byte(`{ 21 | "cities": [ "tokyo", 100, "osaka", 200, "hakata", 300 ], 22 | "data": { 23 | "custom": [ "male", 23, "female", 24 ] 24 | } 25 | }`), &v) 26 | 27 | // s == "tokyo", got a string. 28 | s, _ := dproxy.New(v).M("cities").A(0).String() 29 | 30 | // err != nil, type not matched. 31 | _, err := dproxy.New(v).M("cities").A(0).Float64() 32 | 33 | // n == 200, got a float64 34 | n, _ := dproxy.New(v).M("cities").A(3).Float64() 35 | 36 | // can be chained. 37 | dproxy.New(v).M("data").M("custom").A(0).String() 38 | 39 | // err.Error() == "not found: data.kustom", wrong query can be verified. 40 | _, err = dproxy.New(v).M("data").M("kustom").String() 41 | ``` 42 | 43 | 44 | ## Getting started 45 | 46 | ### Proxy 47 | 48 | 1. Wrap a value (`interface{}`) with `dproxy.New()` get `dproxy.Proxy`. 49 | 50 | ```go 51 | p := dproxy.New(v) // v should be a value of interface{} 52 | ``` 53 | 54 | 2. Query as a map (`map[string]interface{}`)by `M()`, returns `dproxy.Proxy`. 55 | 56 | ```go 57 | p.M("cities") 58 | ``` 59 | 60 | 3. Query as an array (`[]interface{}`) with `A()`, returns `dproxy.Proxy`. 61 | 62 | ```go 63 | p.A(3) 64 | ``` 65 | 66 | 4. Therefore, can be chained queries. 67 | 68 | ```go 69 | p.M("cities").A(3) 70 | ``` 71 | 72 | 5. Get a value finally. 73 | 74 | ```go 75 | n, _ := p.M("cities").A(3).Int64() 76 | ``` 77 | 78 | 6. You'll get an error when getting a value, if there were some mistakes. 79 | 80 | ```go 81 | // OOPS! "kustom" is typo, must be "custom" 82 | _, err := p.M("data").M("kustom").A(3).Int64() 83 | 84 | // "not found: data.kustom" 85 | fmt.Println(err) 86 | ``` 87 | 88 | 7. If you tried to get a value as different type, get an error. 89 | 90 | ```go 91 | // OOPS! "cities[3]" (=200) should be float64 or int64. 92 | _, err := p.M("cities").A(3).String() 93 | 94 | // "not matched types: expected=string actual=float64: cities[3]" 95 | fmt.Println(err) 96 | ``` 97 | 98 | 8. You can verify queries easily. 99 | 100 | ### Drain 101 | 102 | Getting value and error from Proxy/ProxySet multiple times, is very awful. 103 | It must check error when every getting values. 104 | 105 | ```go 106 | p := dproxy.New(v) 107 | v1, err := p.M("cities").A(3).Int64() 108 | if err != nil { 109 | return err 110 | } 111 | v2, err := p.M("data").M("kustom").A(3).Int64() 112 | if err != nil { 113 | return err 114 | } 115 | v3, err := p.M("cities").A(3).String() 116 | if err != nil { 117 | return err 118 | } 119 | ``` 120 | 121 | It can be rewrite as simple like below with `dproxy.Drain` 122 | 123 | ```go 124 | var d Drain 125 | p := dproxy.New(v) 126 | v1 := d.Int64(p.M("cities").A(3)) 127 | v2 := d.Int64(p.M("data").M("kustom").A(3)) 128 | v3 := d.String(p.M("cities").A(3)) 129 | if err := d.CombineErrors(); err != nil { 130 | return err 131 | } 132 | ``` 133 | 134 | ### JSON Pointer 135 | 136 | JSON Pointer can be used to query `interface{}` 137 | 138 | ```go 139 | v1, err := dproxy.New(v).P("/cities/0").Int64() 140 | ``` 141 | 142 | or 143 | 144 | ```go 145 | v1, err := dproxy.Pointer(v, "/cities/0").Int64() 146 | ``` 147 | 148 | See [RFC6901][1] for details of JSON Pointer. 149 | 150 | 151 | ## LICENSE 152 | 153 | MIT license. See LICENSE. 154 | 155 | [1]: https://tools.ietf.org/html/rfc6901 156 | -------------------------------------------------------------------------------- /doc.go: -------------------------------------------------------------------------------- 1 | /* 2 | Package dproxy provides a proxy to adccess `interface{}` (document) by simple 3 | query. It is intented to be used with `"encoding/json".Unmarshal()` or so. 4 | */ 5 | package dproxy 6 | -------------------------------------------------------------------------------- /drain.go: -------------------------------------------------------------------------------- 1 | package dproxy 2 | 3 | import "bytes" 4 | 5 | // Drain stores errors from Proxy or ProxySet. 6 | type Drain struct { 7 | errors []error 8 | } 9 | 10 | // Has returns true if the drain stored some of errors. 11 | func (d *Drain) Has() bool { 12 | return d != nil && len(d.errors) > 0 13 | } 14 | 15 | // First returns a stored error. Returns nil if there are no errors. 16 | func (d *Drain) First() error { 17 | if d == nil || len(d.errors) == 0 { 18 | return nil 19 | } 20 | return d.errors[0] 21 | } 22 | 23 | // All returns all errors which stored. Return nil if no errors stored. 24 | func (d *Drain) All() []error { 25 | if d == nil || len(d.errors) == 0 { 26 | return nil 27 | } 28 | a := make([]error, 0, len(d.errors)) 29 | return append(a, d.errors...) 30 | } 31 | 32 | // CombineErrors returns an error which combined all stored errors. Return nil 33 | // if not erros stored. 34 | func (d *Drain) CombineErrors() error { 35 | if d == nil || len(d.errors) == 0 { 36 | return nil 37 | } 38 | return drainError(d.errors) 39 | } 40 | 41 | func (d *Drain) put(err error) { 42 | if err == nil { 43 | return 44 | } 45 | d.errors = append(d.errors, err) 46 | } 47 | 48 | // Bool returns bool value and stores an error. 49 | func (d *Drain) Bool(p Proxy) bool { 50 | v, err := p.Bool() 51 | d.put(err) 52 | return v 53 | } 54 | 55 | // Int64 returns int64 value and stores an error. 56 | func (d *Drain) Int64(p Proxy) int64 { 57 | v, err := p.Int64() 58 | d.put(err) 59 | return v 60 | } 61 | 62 | // Float64 returns float64 value and stores an error. 63 | func (d *Drain) Float64(p Proxy) float64 { 64 | v, err := p.Float64() 65 | d.put(err) 66 | return v 67 | } 68 | 69 | // String returns string value and stores an error. 70 | func (d *Drain) String(p Proxy) string { 71 | v, err := p.String() 72 | d.put(err) 73 | return v 74 | } 75 | 76 | // Array returns []interface{} value and stores an error. 77 | func (d *Drain) Array(p Proxy) []interface{} { 78 | v, err := p.Array() 79 | d.put(err) 80 | return v 81 | } 82 | 83 | // Map returns map[string]interface{} value and stores an error. 84 | func (d *Drain) Map(p Proxy) map[string]interface{} { 85 | v, err := p.Map() 86 | d.put(err) 87 | return v 88 | } 89 | 90 | // BoolArray returns []bool value and stores an error. 91 | func (d *Drain) BoolArray(ps ProxySet) []bool { 92 | v, err := ps.BoolArray() 93 | d.put(err) 94 | return v 95 | } 96 | 97 | // Int64Array returns []int64 value and stores an error. 98 | func (d *Drain) Int64Array(ps ProxySet) []int64 { 99 | v, err := ps.Int64Array() 100 | d.put(err) 101 | return v 102 | } 103 | 104 | // Float64Array returns []float64 value and stores an error. 105 | func (d *Drain) Float64Array(ps ProxySet) []float64 { 106 | v, err := ps.Float64Array() 107 | d.put(err) 108 | return v 109 | } 110 | 111 | // StringArray returns []string value and stores an error. 112 | func (d *Drain) StringArray(ps ProxySet) []string { 113 | v, err := ps.StringArray() 114 | d.put(err) 115 | return v 116 | } 117 | 118 | // ArrayArray returns [][]interface{} value and stores an error. 119 | func (d *Drain) ArrayArray(ps ProxySet) [][]interface{} { 120 | v, err := ps.ArrayArray() 121 | d.put(err) 122 | return v 123 | } 124 | 125 | // MapArray returns []map[string]interface{} value and stores an error. 126 | func (d *Drain) MapArray(ps ProxySet) []map[string]interface{} { 127 | v, err := ps.MapArray() 128 | d.put(err) 129 | return v 130 | } 131 | 132 | // ProxyArray returns []Proxy value and stores an error. 133 | func (d *Drain) ProxyArray(ps ProxySet) []Proxy { 134 | v, err := ps.ProxyArray() 135 | d.put(err) 136 | return v 137 | } 138 | 139 | type drainError []error 140 | 141 | func (derr drainError) Error() string { 142 | b := bytes.Buffer{} 143 | for i, err := range derr { 144 | if i > 0 { 145 | _, _ = b.WriteString("; ") 146 | } 147 | _, _ = b.WriteString(err.Error()) 148 | } 149 | return b.String() 150 | } 151 | -------------------------------------------------------------------------------- /drain_test.go: -------------------------------------------------------------------------------- 1 | package dproxy 2 | 3 | import ( 4 | "testing" 5 | ) 6 | 7 | func TestDrainBool(t *testing.T) { 8 | v := parseJSON(`{ 9 | "foo": true, 10 | "bar": false 11 | }`) 12 | 13 | d := new(Drain) 14 | 15 | foo := d.Bool(New(v).M("foo")) 16 | if d.Has() { 17 | t.Error(d.First()) 18 | } else if foo != true { 19 | t.Errorf("foo must be true") 20 | } 21 | 22 | bar := d.Bool(New(v).M("bar")) 23 | if d.Has() { 24 | t.Error(d.First()) 25 | } else if bar != false { 26 | t.Errorf("bar must be false") 27 | } 28 | 29 | baz := d.Bool(New(v).M("baz")) 30 | if !d.Has() { 31 | t.Error("baz must not exist") 32 | } else if err := d.First(); err == nil || err.Error() != "not found: baz" { 33 | t.Errorf("unexpected error: %s", err) 34 | } 35 | _ = baz 36 | } 37 | -------------------------------------------------------------------------------- /error.go: -------------------------------------------------------------------------------- 1 | package dproxy 2 | 3 | import ( 4 | "fmt" 5 | "strconv" 6 | ) 7 | 8 | // ErrorType is type of errors 9 | type ErrorType int 10 | 11 | const ( 12 | // Etype means expected type is not matched with actual. 13 | Etype ErrorType = iota + 1 14 | 15 | // Enotfound means key or index doesn't exist. 16 | Enotfound 17 | 18 | // EmapNorArray means target is not a map nor an array (for JSON Pointer) 19 | EmapNorArray 20 | 21 | // EconvertFailure means value conversion is failed. 22 | EconvertFailure 23 | 24 | // EinvalidIndex means token is invalid as index (for JSON Pointer) 25 | EinvalidIndex 26 | 27 | // EinvalidQuery means query is invalid as JSON Pointer. 28 | EinvalidQuery 29 | 30 | // ErequiredType means the type mismatch against user required one. 31 | // For example M() requires map, A() requires array. 32 | ErequiredType 33 | ) 34 | 35 | func (et ErrorType) String() string { 36 | switch et { 37 | case Etype: 38 | return "Etype" 39 | case Enotfound: 40 | return "Enotfound" 41 | case EmapNorArray: 42 | return "EmapNorArray" 43 | case EconvertFailure: 44 | return "EconvertFailure" 45 | case EinvalidIndex: 46 | return "EinvalidIndex" 47 | case EinvalidQuery: 48 | return "EinvalidQuery" 49 | case ErequiredType: 50 | return "ErequiredType" 51 | default: 52 | return "Eunknown" 53 | } 54 | } 55 | 56 | // Error get detail information of the errror. 57 | type Error interface { 58 | // ErrorType returns type of error. 59 | ErrorType() ErrorType 60 | 61 | // FullAddress returns query string where cause first error. 62 | FullAddress() string 63 | } 64 | 65 | type errorProxy struct { 66 | errorType ErrorType 67 | parent frame 68 | label string 69 | 70 | expected Type 71 | actual Type 72 | infoStr string 73 | } 74 | 75 | // errorProxy implements error, Proxy and ProxySet. 76 | var ( 77 | _ error = (*errorProxy)(nil) 78 | _ Proxy = (*errorProxy)(nil) 79 | _ ProxySet = (*errorProxy)(nil) 80 | ) 81 | 82 | func (p *errorProxy) Nil() bool { 83 | return false 84 | } 85 | 86 | func (p *errorProxy) Value() (interface{}, error) { 87 | return nil, p 88 | } 89 | 90 | func (p *errorProxy) Bool() (bool, error) { 91 | return false, p 92 | } 93 | 94 | func (p *errorProxy) Int64() (int64, error) { 95 | return 0, p 96 | } 97 | 98 | func (p *errorProxy) Float64() (float64, error) { 99 | return 0, p 100 | } 101 | 102 | func (p *errorProxy) String() (string, error) { 103 | return "", p 104 | } 105 | 106 | func (p *errorProxy) Array() ([]interface{}, error) { 107 | return nil, p 108 | } 109 | 110 | func (p *errorProxy) Map() (map[string]interface{}, error) { 111 | return nil, p 112 | } 113 | 114 | func (p *errorProxy) A(n int) Proxy { 115 | return p 116 | } 117 | 118 | func (p *errorProxy) M(k string) Proxy { 119 | return p 120 | } 121 | 122 | func (p *errorProxy) P(q string) Proxy { 123 | return p 124 | } 125 | 126 | func (p *errorProxy) Empty() bool { 127 | return true 128 | } 129 | 130 | func (p *errorProxy) Len() int { 131 | return 0 132 | } 133 | 134 | func (p *errorProxy) BoolArray() ([]bool, error) { 135 | return nil, p 136 | } 137 | 138 | func (p *errorProxy) Int64Array() ([]int64, error) { 139 | return nil, p 140 | } 141 | 142 | func (p *errorProxy) Float64Array() ([]float64, error) { 143 | return nil, p 144 | } 145 | 146 | func (p *errorProxy) StringArray() ([]string, error) { 147 | return nil, p 148 | } 149 | 150 | func (p *errorProxy) ArrayArray() ([][]interface{}, error) { 151 | return nil, p 152 | } 153 | 154 | func (p *errorProxy) MapArray() ([]map[string]interface{}, error) { 155 | return nil, p 156 | } 157 | 158 | func (p *errorProxy) ProxyArray() ([]Proxy, error) { 159 | return nil, p 160 | } 161 | 162 | func (p *errorProxy) ProxySet() ProxySet { 163 | return p 164 | } 165 | 166 | func (p *errorProxy) Q(k string) ProxySet { 167 | return p 168 | } 169 | 170 | func (p *errorProxy) Qc(k string) ProxySet { 171 | return p 172 | } 173 | 174 | func (p *errorProxy) findJPT(t string) Proxy { 175 | return p 176 | } 177 | 178 | func (p *errorProxy) parentFrame() frame { 179 | return p.parent 180 | } 181 | 182 | func (p *errorProxy) frameLabel() string { 183 | return p.label 184 | } 185 | 186 | func (p *errorProxy) Error() string { 187 | switch p.errorType { 188 | case Etype: 189 | return fmt.Sprintf("not matched types: expected=%s actual=%s: %s", 190 | p.expected.String(), p.actual.String(), p.FullAddress()) 191 | case Enotfound: 192 | return fmt.Sprintf("not found: %s", p.FullAddress()) 193 | case EmapNorArray: 194 | // FIXME: better error message. 195 | return fmt.Sprintf("not map nor array: actual=%s: %s", 196 | p.actual.String(), p.FullAddress()) 197 | case EconvertFailure: 198 | return fmt.Sprintf("convert error: %s: %s", p.infoStr, p.FullAddress()) 199 | case EinvalidIndex: 200 | // FIXME: better error message. 201 | return fmt.Sprintf("invalid index: %s: %s", p.infoStr, p.FullAddress()) 202 | case EinvalidQuery: 203 | // FIXME: better error message. 204 | return fmt.Sprintf("invalid query: %s: %s", p.infoStr, p.FullAddress()) 205 | case ErequiredType: 206 | return fmt.Sprintf("not required types: required=%s actual=%s: %s", 207 | p.expected.String(), p.actual.String(), p.FullAddress()) 208 | default: 209 | return fmt.Sprintf("unexpected: %s", p.FullAddress()) 210 | } 211 | } 212 | 213 | func (p *errorProxy) ErrorType() ErrorType { 214 | return p.errorType 215 | } 216 | 217 | func (p *errorProxy) FullAddress() string { 218 | return fullAddress(p) 219 | } 220 | 221 | func typeError(p frame, expected Type, actual interface{}) *errorProxy { 222 | return &errorProxy{ 223 | errorType: Etype, 224 | parent: p, 225 | expected: expected, 226 | actual: detectType(actual), 227 | } 228 | } 229 | 230 | func requiredTypeError(p frame, expected Type, actual interface{}) *errorProxy { 231 | return &errorProxy{ 232 | errorType: ErequiredType, 233 | parent: p, 234 | expected: expected, 235 | actual: detectType(actual), 236 | } 237 | } 238 | 239 | func elementTypeError(p frame, index int, expected Type, actual interface{}) *errorProxy { 240 | q := &simpleFrame{ 241 | parent: p, 242 | label: "[" + strconv.Itoa(index) + "]", 243 | } 244 | return typeError(q, expected, actual) 245 | } 246 | 247 | func notfoundError(p frame, address string) *errorProxy { 248 | return &errorProxy{ 249 | errorType: Enotfound, 250 | parent: p, 251 | label: address, 252 | } 253 | } 254 | -------------------------------------------------------------------------------- /frame.go: -------------------------------------------------------------------------------- 1 | package dproxy 2 | 3 | type frame interface { 4 | // parentFrame returns parent frame. 5 | parentFrame() frame 6 | // frameLabel return label of frame. 7 | frameLabel() string 8 | } 9 | 10 | func fullAddress(f frame) string { 11 | x := 0 12 | for g := f; g != nil; g = g.parentFrame() { 13 | x += len(g.frameLabel()) 14 | } 15 | if x == 0 { 16 | return "(root)" 17 | } 18 | b := make([]byte, x) 19 | for g := f; g != nil; g = g.parentFrame() { 20 | x -= len(g.frameLabel()) 21 | copy(b[x:], g.frameLabel()) 22 | } 23 | if b[0] == '.' { 24 | return string(b[1:]) 25 | } 26 | return string(b) 27 | } 28 | 29 | type simpleFrame struct { 30 | parent frame 31 | label string 32 | } 33 | 34 | var _ frame = (*simpleFrame)(nil) 35 | 36 | func (f *simpleFrame) parentFrame() frame { 37 | return f.parent 38 | } 39 | 40 | func (f *simpleFrame) frameLabel() string { 41 | return f.label 42 | } 43 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/koron/go-dproxy 2 | 3 | go 1.13 4 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/koron/go-dproxy/ce67716de069b52c4450eb574d4684cce5af8d8f/go.sum -------------------------------------------------------------------------------- /package_test.go: -------------------------------------------------------------------------------- 1 | package dproxy 2 | 3 | import ( 4 | "fmt" 5 | "reflect" 6 | "testing" 7 | ) 8 | 9 | func assertEquals(t *testing.T, actual, expected interface{}, format string, a ...interface{}) { 10 | t.Helper() 11 | if !reflect.DeepEqual(actual, expected) { 12 | msg := fmt.Sprintf(format, a...) 13 | t.Errorf("not equal: %s\nexpect=%+v\nactual=%+v", msg, expected, actual) 14 | } 15 | } 16 | 17 | func assertError(t *testing.T, err error, exp string) { 18 | t.Helper() 19 | if err == nil { 20 | t.Fatalf("should fail with: %s", exp) 21 | } 22 | if act := err.Error(); act != exp { 23 | t.Fatalf("unexpected error:\nexpect=%s\nactual=%s\n", exp, act) 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /pointer.go: -------------------------------------------------------------------------------- 1 | package dproxy 2 | 3 | import "strings" 4 | 5 | var jptR = strings.NewReplacer("~1", "/", "~0", "~") 6 | 7 | func unescapeJPT(t string) string { 8 | return jptR.Replace(t) 9 | } 10 | 11 | func pointer(p Proxy, q string) Proxy { 12 | if len(q) == 0 { 13 | return p 14 | } 15 | if q[0] != '/' { 16 | return &errorProxy{ 17 | errorType: EinvalidQuery, 18 | parent: p, 19 | infoStr: "not start with '/'", 20 | } 21 | } 22 | for _, t := range strings.Split(q[1:], "/") { 23 | p = p.findJPT(unescapeJPT(t)) 24 | } 25 | return p 26 | } 27 | 28 | // Pointer returns a Proxy which pointed by JSON Pointer's query q 29 | func Pointer(v interface{}, q string) Proxy { 30 | return pointer(New(v), q) 31 | } 32 | -------------------------------------------------------------------------------- /pointer_test.go: -------------------------------------------------------------------------------- 1 | package dproxy 2 | 3 | import "testing" 4 | 5 | func TestUnescapeJPT(t *testing.T) { 6 | f := func(d, expect string) { 7 | s := unescapeJPT(d) 8 | if s != expect { 9 | t.Errorf("unescapeJPT(%q) should be %q but actually %q", d, expect, s) 10 | } 11 | } 12 | f("foo", "foo") 13 | f("bar", "bar") 14 | f("~0", "~") 15 | f("foo~0bar", "foo~bar") 16 | f("~1", "/") 17 | f("foo~1bar", "foo/bar") 18 | f("~01", "~1") 19 | f("foo~01bar", "foo~1bar") 20 | f("~10", "/0") 21 | } 22 | 23 | func TestPointerInvalidQuery(t *testing.T) { 24 | p := Pointer(nil, "invalid") 25 | err, ok := p.(*errorProxy) 26 | if !ok { 27 | t.Fatalf("it should be *errorProxy but: %+v", p) 28 | } 29 | if err.errorType != EinvalidQuery { 30 | t.Fatalf("errorType should be EinvalidQuery but: %s", err.errorType) 31 | } 32 | } 33 | 34 | func TestPointer(t *testing.T) { 35 | f := func(q string, d, expected interface{}) { 36 | p := Pointer(d, q) 37 | v, err := p.Value() 38 | if err != nil { 39 | t.Errorf("Pointer:%q for %+v failed: %s", q, d, err) 40 | return 41 | } 42 | assertEquals(t, v, expected, "Pointer:%q for %+v", q, d) 43 | } 44 | 45 | v := parseJSON(`{ 46 | "cities": [ "tokyo", 100, "osaka", 200, "hakata", 300 ], 47 | "data": { 48 | "custom": [ "male", 21, "female", 22 ] 49 | } 50 | }`) 51 | f("", v, v) 52 | f("/cities", v, parseJSON(`["tokyo",100,"osaka",200,"hakata",300]`)) 53 | f("/cities/0", v, "tokyo") 54 | f("/cities/1", v, float64(100)) 55 | f("/cities/2", v, "osaka") 56 | f("/cities/3", v, float64(200)) 57 | f("/cities/4", v, "hakata") 58 | f("/cities/5", v, float64(300)) 59 | f("/data/custom", v, parseJSON(`["male",21,"female",22]`)) 60 | 61 | // Example from RFC6901 https://tools.ietf.org/html/rfc6901 62 | w := parseJSON(`{ 63 | "foo": ["bar", "baz"], 64 | "": 0, 65 | "a/b": 1, 66 | "c%d": 2, 67 | "e^f": 3, 68 | "g|h": 4, 69 | "i\\j": 5, 70 | "k\"l": 6, 71 | " ": 7, 72 | "m~n": 8 73 | }`) 74 | f("", w, w) 75 | f("/foo", w, parseJSON(`["bar","baz"]`)) 76 | f("/foo/0", w, "bar") 77 | f("/", w, float64(0)) 78 | f("/a~1b", w, float64(1)) 79 | f("/c%d", w, float64(2)) 80 | f("/e^f", w, float64(3)) 81 | f("/g|h", w, float64(4)) 82 | f("/i\\j", w, float64(5)) 83 | f("/k\"l", w, float64(6)) 84 | f("/ ", w, float64(7)) 85 | f("/m~0n", w, float64(8)) 86 | } 87 | -------------------------------------------------------------------------------- /proxy.go: -------------------------------------------------------------------------------- 1 | package dproxy 2 | 3 | // Proxy is a proxy to access a document (interface{}). 4 | type Proxy interface { 5 | // Nil returns true, if target value is nil. 6 | Nil() bool 7 | 8 | // Value returns a proxied value. If there are no values, it returns 9 | // error. 10 | Value() (interface{}, error) 11 | 12 | // Bool returns its value. If value isn't the type, it returns error. 13 | Bool() (bool, error) 14 | 15 | // Int64 returns its value. If value isn't the type, it returns error. 16 | Int64() (int64, error) 17 | 18 | // Float64 returns its value. If value isn't the type, it returns error. 19 | Float64() (float64, error) 20 | 21 | // String returns its value. If value isn't the type, it returns error. 22 | String() (string, error) 23 | 24 | // Array returns its value. If value isn't the type, it returns error. 25 | Array() ([]interface{}, error) 26 | 27 | // Map returns its value. If value isn't the type, it returns error. 28 | Map() (map[string]interface{}, error) 29 | 30 | // A returns an item from value treated as the array. 31 | A(n int) Proxy 32 | 33 | // M returns an item from value treated as the map. 34 | M(k string) Proxy 35 | 36 | // P returns which pointed by JSON Pointer's query q. 37 | P(q string) Proxy 38 | 39 | // ProxySet returns a set which converted from its array value. 40 | ProxySet() ProxySet 41 | 42 | // Q returns set of all items which property matchs with k. 43 | Q(k string) ProxySet 44 | 45 | // findJPT returns a match of JSON Pointer's Token t. 46 | findJPT(t string) Proxy 47 | 48 | // Proxy implements frame. 49 | frame 50 | } 51 | 52 | // ProxySet proxies to access to set. 53 | type ProxySet interface { 54 | // Empty returns true when the set is empty. 55 | Empty() bool 56 | 57 | // Len returns count of items in the set. 58 | Len() int 59 | 60 | // BoolArray returns []bool which converterd from the set. 61 | BoolArray() ([]bool, error) 62 | 63 | // Int64Array returns []int64 which converterd from the set. 64 | Int64Array() ([]int64, error) 65 | 66 | // Float64Array returns []float64 which converterd from the set. 67 | Float64Array() ([]float64, error) 68 | 69 | // StringArray returns []string which converterd from the set. 70 | StringArray() ([]string, error) 71 | 72 | // ArrayArray returns [][]interface{} which converterd from the set. 73 | ArrayArray() ([][]interface{}, error) 74 | 75 | // MapArray returns []map[string]interface{} which converterd from the set. 76 | MapArray() ([]map[string]interface{}, error) 77 | 78 | // ProxyArray returns []Proxy which wrap each items. 79 | ProxyArray() ([]Proxy, error) 80 | 81 | // A returns an proxy for index in the set. 82 | A(n int) Proxy 83 | 84 | // Q returns set of all items which property matchs with k. 85 | Q(k string) ProxySet 86 | 87 | // Qc returns set of property of all items. 88 | Qc(k string) ProxySet 89 | 90 | // Proxy implements frame. 91 | frame 92 | } 93 | 94 | // New creates a new Proxy instance for v. 95 | func New(v interface{}) Proxy { 96 | return &valueProxy{value: v} 97 | } 98 | 99 | // NewSet create a new ProxySet instance for v. 100 | func NewSet(v []interface{}) ProxySet { 101 | return &setProxy{values: v} 102 | } 103 | -------------------------------------------------------------------------------- /proxy_test.go: -------------------------------------------------------------------------------- 1 | package dproxy 2 | 3 | import ( 4 | "encoding/json" 5 | "testing" 6 | ) 7 | 8 | func parseJSON(s string) interface{} { 9 | var v interface{} 10 | if err := json.Unmarshal([]byte(s), &v); err != nil { 11 | panic(err) 12 | } 13 | return v 14 | } 15 | 16 | func equalStrings(a, b []string) bool { 17 | if len(a) != len(b) { 18 | return false 19 | } 20 | for i, s := range a { 21 | if s != b[i] { 22 | return false 23 | } 24 | } 25 | return true 26 | } 27 | 28 | func equalInts(a, b []int64) bool { 29 | if len(a) != len(b) { 30 | return false 31 | } 32 | for i, s := range a { 33 | if s != b[i] { 34 | return false 35 | } 36 | } 37 | return true 38 | } 39 | 40 | func TestReadme(t *testing.T) { 41 | v := parseJSON(`{ 42 | "cities": [ "tokyo", 100, "osaka", 200, "hakata", 300 ], 43 | "data": { 44 | "custom": [ "male", 21, "female", 22 ] 45 | } 46 | }`) 47 | 48 | s, err := New(v).M("cities").A(0).String() 49 | if s != "tokyo" { 50 | t.Error("cities[0] must be \"tokyo\":", err) 51 | } 52 | 53 | _, err = New(v).M("cities").A(0).Float64() 54 | if err == nil { 55 | t.Error("cities[0] (float64) must be failed:", err) 56 | } 57 | 58 | n, err := New(v).M("cities").A(1).Float64() 59 | if n != 100 { 60 | t.Error("cities[1] must be 100:", err) 61 | } 62 | 63 | s2, err := New(v).M("data").M("custom").A(2).String() 64 | if s2 != "female" { 65 | t.Error("data.custom[2] must be \"female\":", err) 66 | } 67 | 68 | _, err = New(v).M("data").M("kustom").String() 69 | if err == nil || err.Error() != "not found: data.kustom" { 70 | t.Error("err is not \"not found: data.kustom\":", err) 71 | } 72 | } 73 | 74 | func TestMapBool(t *testing.T) { 75 | v := parseJSON(`{ 76 | "foo": true, 77 | "bar": false 78 | }`) 79 | 80 | // check "foo" 81 | foo, err := New(v).M("foo").Bool() 82 | if err != nil { 83 | t.Error(err) 84 | } else if foo != true { 85 | t.Errorf("foo must be true") 86 | } 87 | 88 | // check "bar" 89 | bar, err := New(v).M("bar").Bool() 90 | if err != nil { 91 | t.Error(err) 92 | } else if bar != false { 93 | t.Errorf("bar must be false") 94 | } 95 | } 96 | 97 | type wrappedMap map[string]interface{} 98 | 99 | func TestWrappedMap(t *testing.T) { 100 | v := wrappedMap{ 101 | "foo": 123, 102 | } 103 | n, err := New(v).M("foo").Int64() 104 | if err != nil { 105 | t.Fatalf("failed: %s", err) 106 | } 107 | if n != 123 { 108 | t.Fatalf("unexpected value: %d", n) 109 | } 110 | } 111 | -------------------------------------------------------------------------------- /set.go: -------------------------------------------------------------------------------- 1 | package dproxy 2 | 3 | import "strconv" 4 | 5 | type setProxy struct { 6 | values []interface{} 7 | parent frame 8 | label string 9 | } 10 | 11 | // setProxy implements ProxySet 12 | var _ ProxySet = (*setProxy)(nil) 13 | 14 | func (p *setProxy) Empty() bool { 15 | return p.Len() == 0 16 | } 17 | 18 | func (p *setProxy) Len() int { 19 | return len(p.values) 20 | } 21 | 22 | func (p *setProxy) BoolArray() ([]bool, error) { 23 | r := make([]bool, len(p.values)) 24 | for i, v := range p.values { 25 | switch v := v.(type) { 26 | case bool: 27 | r[i] = v 28 | default: 29 | return nil, elementTypeError(p, i, Tbool, v) 30 | } 31 | } 32 | return r, nil 33 | } 34 | 35 | func (p *setProxy) Int64Array() ([]int64, error) { 36 | r := make([]int64, len(p.values)) 37 | for i, v := range p.values { 38 | switch v := v.(type) { 39 | case int: 40 | r[i] = int64(v) 41 | case int32: 42 | r[i] = int64(v) 43 | case int64: 44 | r[i] = v 45 | case float32: 46 | r[i] = int64(v) 47 | case float64: 48 | r[i] = int64(v) 49 | default: 50 | return nil, elementTypeError(p, i, Tint64, v) 51 | } 52 | } 53 | return r, nil 54 | } 55 | 56 | func (p *setProxy) Float64Array() ([]float64, error) { 57 | r := make([]float64, len(p.values)) 58 | for i, v := range p.values { 59 | switch v := v.(type) { 60 | case int: 61 | r[i] = float64(v) 62 | case int32: 63 | r[i] = float64(v) 64 | case int64: 65 | r[i] = float64(v) 66 | case float32: 67 | r[i] = float64(v) 68 | case float64: 69 | r[i] = v 70 | default: 71 | return nil, elementTypeError(p, i, Tfloat64, v) 72 | } 73 | } 74 | return r, nil 75 | } 76 | 77 | func (p *setProxy) StringArray() ([]string, error) { 78 | r := make([]string, len(p.values)) 79 | for i, v := range p.values { 80 | switch v := v.(type) { 81 | case string: 82 | r[i] = v 83 | default: 84 | return nil, elementTypeError(p, i, Tstring, v) 85 | } 86 | } 87 | return r, nil 88 | } 89 | 90 | func (p *setProxy) ArrayArray() ([][]interface{}, error) { 91 | r := make([][]interface{}, len(p.values)) 92 | for i, v := range p.values { 93 | switch v := v.(type) { 94 | case []interface{}: 95 | r[i] = v 96 | default: 97 | return nil, elementTypeError(p, i, Tarray, v) 98 | } 99 | } 100 | return r, nil 101 | } 102 | 103 | func (p *setProxy) MapArray() ([]map[string]interface{}, error) { 104 | r := make([]map[string]interface{}, len(p.values)) 105 | for i, v := range p.values { 106 | switch v := v.(type) { 107 | case map[string]interface{}: 108 | r[i] = v 109 | default: 110 | return nil, elementTypeError(p, i, Tmap, v) 111 | } 112 | } 113 | return r, nil 114 | } 115 | 116 | func (p *setProxy) ProxyArray() ([]Proxy, error) { 117 | r := make([]Proxy, 0, len(p.values)) 118 | for i, v := range p.values { 119 | r = append(r, &valueProxy{ 120 | value: v, 121 | parent: p, 122 | label: "[" + strconv.Itoa(i) + "]", 123 | }) 124 | } 125 | return r, nil 126 | } 127 | 128 | func (p *setProxy) A(n int) Proxy { 129 | a := "[" + strconv.Itoa(n) + "]" 130 | if n < 0 || n >= len(p.values) { 131 | return notfoundError(p, a) 132 | } 133 | return &valueProxy{ 134 | value: p.values[n], 135 | parent: p, 136 | label: a, 137 | } 138 | } 139 | 140 | func (p *setProxy) Q(k string) ProxySet { 141 | w := findAll(p.values, k) 142 | return &setProxy{ 143 | values: w, 144 | parent: p, 145 | label: ".." + k, 146 | } 147 | } 148 | 149 | func (p *setProxy) Qc(k string) ProxySet { 150 | r := make([]interface{}, 0, len(p.values)) 151 | for _, v := range p.values { 152 | switch v := v.(type) { 153 | case map[string]interface{}: 154 | if w, ok := v[k]; ok { 155 | r = append(r, w) 156 | } 157 | } 158 | } 159 | return &setProxy{ 160 | values: r, 161 | parent: p, 162 | label: ".." + k, 163 | } 164 | } 165 | 166 | func (p *setProxy) parentFrame() frame { 167 | return p.parent 168 | } 169 | 170 | func (p *setProxy) frameLabel() string { 171 | return p.label 172 | } 173 | 174 | func findAll(v interface{}, k string) []interface{} { 175 | return findAllImpl(v, k, make([]interface{}, 0, 10)) 176 | } 177 | 178 | func findAllImpl(v interface{}, k string, r []interface{}) []interface{} { 179 | switch v := v.(type) { 180 | case map[string]interface{}: 181 | for n, w := range v { 182 | if n == k { 183 | r = append(r, w) 184 | } 185 | r = findAllImpl(w, k, r) 186 | } 187 | case []interface{}: 188 | for _, w := range v { 189 | r = findAllImpl(w, k, r) 190 | } 191 | } 192 | return r 193 | } 194 | -------------------------------------------------------------------------------- /set_test.go: -------------------------------------------------------------------------------- 1 | package dproxy 2 | 3 | import "testing" 4 | 5 | func TestSet(t *testing.T) { 6 | v := parseJSON(`{ 7 | "items" : [ 8 | { 9 | "name": "Bob", 10 | "age": 20 11 | }, 12 | { 13 | "name": "Mike", 14 | "age": 23 15 | }, 16 | { 17 | "name": "John", 18 | "age": 22 19 | } 20 | ] 21 | }`) 22 | 23 | names, err := New(v).Q("name").StringArray() 24 | if err != nil { 25 | t.Fatal(err) 26 | } else if !equalStrings(names, []string{"Bob", "Mike", "John"}) { 27 | t.Error("unexpected names:", names) 28 | } 29 | 30 | ages, err := New(v).Q("age").Int64Array() 31 | if err != nil { 32 | t.Fatal(err) 33 | } else if !equalInts(ages, []int64{20, 23, 22}) { 34 | t.Error("unexpected ages:", ages) 35 | } 36 | _ = ages 37 | } 38 | 39 | func TestSetTypeError(t *testing.T) { 40 | v := parseJSON(`[true, false, 0, true, false]`) 41 | _, err := New(v).ProxySet().BoolArray() 42 | if err == nil { 43 | t.Fatal("should fail") 44 | } 45 | err2, ok := err.(Error) 46 | if !ok { 47 | t.Fatal("err is not Error:", err) 48 | } 49 | if et := err2.ErrorType(); et != Etype { 50 | t.Fatal("unexpected ErrorType:", et) 51 | } 52 | if ea := err2.FullAddress(); ea != "[2]" { 53 | t.Fatal("unexpected FullAddress:", ea) 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /staticcheck.conf: -------------------------------------------------------------------------------- 1 | # vim:set ft=toml: 2 | 3 | checks = ["all"] 4 | 5 | # based on: github.com/koron-go/_skeleton/staticcheck.conf 6 | -------------------------------------------------------------------------------- /type.go: -------------------------------------------------------------------------------- 1 | package dproxy 2 | 3 | // Type is type of value. 4 | type Type int 5 | 6 | const ( 7 | // Tunknown shows value is not supported. 8 | Tunknown Type = iota 9 | 10 | // Tnil shows value is nil. 11 | Tnil 12 | 13 | // Tbool shows value is bool. 14 | Tbool 15 | 16 | // Tint64 shows value is int64. 17 | Tint64 18 | 19 | // Tfloat64 shows value is float64. 20 | Tfloat64 21 | 22 | // Tstring shows value is string. 23 | Tstring 24 | 25 | // Tarray shows value is an array ([]interface{}) 26 | Tarray 27 | 28 | // Tmap shows value is a map (map[string]interface{}) 29 | Tmap 30 | ) 31 | 32 | // detectType returns type of a value. 33 | func detectType(v interface{}) Type { 34 | if v == nil { 35 | return Tnil 36 | } 37 | switch v.(type) { 38 | case bool: 39 | return Tbool 40 | case int, int32, int64: 41 | return Tint64 42 | case float32, float64: 43 | return Tfloat64 44 | case string: 45 | return Tstring 46 | case []interface{}: 47 | return Tarray 48 | case map[string]interface{}: 49 | return Tmap 50 | default: 51 | return Tunknown 52 | } 53 | } 54 | 55 | func (t Type) String() string { 56 | switch t { 57 | case Tunknown: 58 | return "unknown" 59 | case Tnil: 60 | return "nil" 61 | case Tbool: 62 | return "bool" 63 | case Tint64: 64 | return "int64" 65 | case Tfloat64: 66 | return "float64" 67 | case Tstring: 68 | return "string" 69 | case Tarray: 70 | return "array" 71 | case Tmap: 72 | return "map" 73 | default: 74 | return "unknown" 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /value.go: -------------------------------------------------------------------------------- 1 | package dproxy 2 | 3 | import ( 4 | "reflect" 5 | "strconv" 6 | ) 7 | 8 | type valueProxy struct { 9 | value interface{} 10 | parent frame 11 | label string 12 | } 13 | 14 | // valueProxy implements Proxy. 15 | var _ Proxy = (*valueProxy)(nil) 16 | 17 | func (p *valueProxy) Nil() bool { 18 | return p.value == nil 19 | } 20 | 21 | func (p *valueProxy) Value() (interface{}, error) { 22 | return p.value, nil 23 | } 24 | 25 | func (p *valueProxy) Bool() (bool, error) { 26 | switch v := p.value.(type) { 27 | case bool: 28 | return v, nil 29 | default: 30 | return false, typeError(p, Tbool, v) 31 | } 32 | } 33 | 34 | type int64er interface { 35 | Int64() (int64, error) 36 | } 37 | 38 | func (p *valueProxy) Int64() (int64, error) { 39 | switch v := p.value.(type) { 40 | case int: 41 | return int64(v), nil 42 | case int32: 43 | return int64(v), nil 44 | case int64: 45 | return v, nil 46 | case float32: 47 | return int64(v), nil 48 | case float64: 49 | return int64(v), nil 50 | case int64er: 51 | w, err := v.Int64() 52 | if err != nil { 53 | return 0, &errorProxy{ 54 | errorType: EconvertFailure, 55 | parent: p, 56 | infoStr: err.Error(), 57 | } 58 | } 59 | return w, nil 60 | default: 61 | return 0, typeError(p, Tint64, v) 62 | } 63 | } 64 | 65 | type float64er interface { 66 | Float64() (float64, error) 67 | } 68 | 69 | func (p *valueProxy) Float64() (float64, error) { 70 | switch v := p.value.(type) { 71 | case int: 72 | return float64(v), nil 73 | case int32: 74 | return float64(v), nil 75 | case int64: 76 | return float64(v), nil 77 | case float32: 78 | return float64(v), nil 79 | case float64: 80 | return v, nil 81 | case float64er: 82 | w, err := v.Float64() 83 | if err != nil { 84 | return 0, &errorProxy{ 85 | errorType: EconvertFailure, 86 | parent: p, 87 | infoStr: err.Error(), 88 | } 89 | } 90 | return w, nil 91 | default: 92 | return 0, typeError(p, Tfloat64, v) 93 | } 94 | } 95 | 96 | func (p *valueProxy) String() (string, error) { 97 | switch v := p.value.(type) { 98 | case string: 99 | return v, nil 100 | default: 101 | return "", typeError(p, Tstring, v) 102 | } 103 | } 104 | 105 | func (p *valueProxy) Array() ([]interface{}, error) { 106 | switch v := p.value.(type) { 107 | case []interface{}: 108 | return v, nil 109 | default: 110 | return nil, typeError(p, Tarray, v) 111 | } 112 | } 113 | 114 | func (p *valueProxy) Map() (map[string]interface{}, error) { 115 | switch v := p.value.(type) { 116 | case map[string]interface{}: 117 | return v, nil 118 | default: 119 | return nil, typeError(p, Tmap, v) 120 | } 121 | } 122 | 123 | func (p *valueProxy) A(n int) Proxy { 124 | switch v := p.value.(type) { 125 | case []interface{}: 126 | a := "[" + strconv.Itoa(n) + "]" 127 | if n < 0 || n >= len(v) { 128 | return notfoundError(p, a) 129 | } 130 | return &valueProxy{ 131 | value: v[n], 132 | parent: p, 133 | label: a, 134 | } 135 | default: 136 | return requiredTypeError(p, Tarray, v) 137 | } 138 | } 139 | 140 | var mapType = reflect.TypeOf(map[string]interface{}(nil)) 141 | 142 | func (p *valueProxy) m(v map[string]interface{}, k string) Proxy { 143 | a := "." + k 144 | w, ok := v[k] 145 | if !ok { 146 | return notfoundError(p, a) 147 | } 148 | return &valueProxy{ 149 | value: w, 150 | parent: p, 151 | label: a, 152 | } 153 | } 154 | 155 | func (p *valueProxy) M(k string) Proxy { 156 | if v, ok := p.value.(map[string]interface{}); ok { 157 | return p.m(v, k) 158 | } 159 | 160 | if rv := reflect.ValueOf(p.value); rv.IsValid() && rv.Type().ConvertibleTo(mapType) { 161 | v, _ := rv.Convert(mapType).Interface().(map[string]interface{}) 162 | return p.m(v, k) 163 | } 164 | 165 | return requiredTypeError(p, Tmap, p.value) 166 | } 167 | 168 | func (p *valueProxy) P(q string) Proxy { 169 | return pointer(p, q) 170 | } 171 | 172 | func (p *valueProxy) ProxySet() ProxySet { 173 | switch v := p.value.(type) { 174 | case []interface{}: 175 | return &setProxy{ 176 | values: v, 177 | parent: p, 178 | } 179 | default: 180 | return typeError(p, Tarray, v) 181 | } 182 | } 183 | 184 | func (p *valueProxy) Q(k string) ProxySet { 185 | w := findAll(p.value, k) 186 | return &setProxy{ 187 | values: w, 188 | parent: p, 189 | label: ".." + k, 190 | } 191 | } 192 | 193 | func (p *valueProxy) findJPT(t string) Proxy { 194 | switch v := p.value.(type) { 195 | case map[string]interface{}: 196 | return p.M(t) 197 | case []interface{}: 198 | n, err := strconv.ParseUint(t, 10, 0) 199 | if err != nil { 200 | return &errorProxy{ 201 | errorType: EinvalidIndex, 202 | parent: p, 203 | infoStr: err.Error(), 204 | } 205 | } 206 | return p.A(int(n)) 207 | default: 208 | return &errorProxy{ 209 | errorType: EmapNorArray, 210 | parent: p, 211 | actual: detectType(v), 212 | } 213 | } 214 | } 215 | 216 | func (p *valueProxy) parentFrame() frame { 217 | return p.parent 218 | } 219 | 220 | func (p *valueProxy) frameLabel() string { 221 | return p.label 222 | } 223 | -------------------------------------------------------------------------------- /value_test.go: -------------------------------------------------------------------------------- 1 | package dproxy 2 | 3 | import "testing" 4 | 5 | func TestTypeError(t *testing.T) { 6 | t.Run("map at root", func(t *testing.T) { 7 | v := &valueProxy{} 8 | _, err := v.M("foo").Int64() 9 | assertError(t, err, "not required types: required=map actual=nil: (root)") 10 | }) 11 | t.Run("map at child", func(t *testing.T) { 12 | v := &valueProxy{ 13 | parent: &valueProxy{}, 14 | label: "foo", 15 | } 16 | _, err := v.M("bar").Int64() 17 | assertError(t, err, "not required types: required=map actual=nil: foo") 18 | }) 19 | 20 | t.Run("array at root", func(t *testing.T) { 21 | v := &valueProxy{} 22 | _, err := v.A(0).Int64() 23 | assertError(t, err, "not required types: required=array actual=nil: (root)") 24 | }) 25 | t.Run("array at child", func(t *testing.T) { 26 | v := &valueProxy{ 27 | parent: &valueProxy{}, 28 | label: "foo", 29 | } 30 | _, err := v.A(0).Int64() 31 | assertError(t, err, "not required types: required=array actual=nil: foo") 32 | }) 33 | } 34 | --------------------------------------------------------------------------------