~nskaggs/+junk/xenial-test

« back to all changes in this revision

Viewing changes to src/github.com/juju/gojsonpointer/pointer.go

  • Committer: Nicholas Skaggs
  • Date: 2016-10-24 20:56:05 UTC
  • Revision ID: nicholas.skaggs@canonical.com-20161024205605-z8lta0uvuhtxwzwl
Initi with beta15

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
// Copyright 2015 xeipuuv ( https://github.com/xeipuuv )
 
2
//
 
3
// Licensed under the Apache License, Version 2.0 (the "License");
 
4
// you may not use this file except in compliance with the License.
 
5
// You may obtain a copy of the License at
 
6
//
 
7
//   http://www.apache.org/licenses/LICENSE-2.0
 
8
//
 
9
// Unless required by applicable law or agreed to in writing, software
 
10
// distributed under the License is distributed on an "AS IS" BASIS,
 
11
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 
12
// See the License for the specific language governing permissions and
 
13
// limitations under the License.
 
14
 
 
15
// author                       xeipuuv
 
16
// author-github        https://github.com/xeipuuv
 
17
// author-mail          xeipuuv@gmail.com
 
18
//
 
19
// repository-name      gojsonpointer
 
20
// repository-desc      An implementation of JSON Pointer - Go language
 
21
//
 
22
// description          Main and unique file.
 
23
//
 
24
// created              25-02-2013
 
25
 
 
26
package gojsonpointer
 
27
 
 
28
import (
 
29
        "errors"
 
30
        "fmt"
 
31
        "reflect"
 
32
        "strconv"
 
33
        "strings"
 
34
)
 
35
 
 
36
const (
 
37
        const_empty_pointer     = ``
 
38
        const_pointer_separator = `/`
 
39
 
 
40
        const_invalid_start = `JSON pointer must be empty or start with a "` + const_pointer_separator
 
41
)
 
42
 
 
43
type implStruct struct {
 
44
        mode string // "SET" or "GET"
 
45
 
 
46
        inDocument interface{}
 
47
 
 
48
        setInValue interface{}
 
49
 
 
50
        getOutNode interface{}
 
51
        getOutKind reflect.Kind
 
52
        outError   error
 
53
}
 
54
 
 
55
func NewJsonPointer(jsonPointerString string) (JsonPointer, error) {
 
56
 
 
57
        var p JsonPointer
 
58
        err := p.parse(jsonPointerString)
 
59
        return p, err
 
60
 
 
61
}
 
62
 
 
63
type JsonPointer struct {
 
64
        referenceTokens []string
 
65
}
 
66
 
 
67
// "Constructor", parses the given string JSON pointer
 
68
func (p *JsonPointer) parse(jsonPointerString string) error {
 
69
 
 
70
        var err error
 
71
 
 
72
        if jsonPointerString != const_empty_pointer {
 
73
                if !strings.HasPrefix(jsonPointerString, const_pointer_separator) {
 
74
                        err = errors.New(const_invalid_start)
 
75
                } else {
 
76
                        referenceTokens := strings.Split(jsonPointerString, const_pointer_separator)
 
77
                        for _, referenceToken := range referenceTokens[1:] {
 
78
                                p.referenceTokens = append(p.referenceTokens, referenceToken)
 
79
                        }
 
80
                }
 
81
        }
 
82
 
 
83
        return err
 
84
}
 
85
 
 
86
// Uses the pointer to retrieve a value from a JSON document
 
87
func (p *JsonPointer) Get(document interface{}) (interface{}, reflect.Kind, error) {
 
88
 
 
89
        is := &implStruct{mode: "GET", inDocument: document}
 
90
        p.implementation(is)
 
91
        return is.getOutNode, is.getOutKind, is.outError
 
92
 
 
93
}
 
94
 
 
95
// Uses the pointer to update a value from a JSON document
 
96
func (p *JsonPointer) Set(document interface{}, value interface{}) (interface{}, error) {
 
97
 
 
98
        is := &implStruct{mode: "SET", inDocument: document, setInValue: value}
 
99
        p.implementation(is)
 
100
        return document, is.outError
 
101
 
 
102
}
 
103
 
 
104
// Both Get and Set functions use the same implementation to avoid code duplication
 
105
func (p *JsonPointer) implementation(i *implStruct) {
 
106
 
 
107
        kind := reflect.Invalid
 
108
 
 
109
        // Full document when empty
 
110
        if len(p.referenceTokens) == 0 {
 
111
                i.getOutNode = i.inDocument
 
112
                i.outError = nil
 
113
                i.getOutKind = kind
 
114
                i.outError = nil
 
115
                return
 
116
        }
 
117
 
 
118
        node := i.inDocument
 
119
 
 
120
        for ti, token := range p.referenceTokens {
 
121
 
 
122
                decodedToken := decodeReferenceToken(token)
 
123
                isLastToken := ti == len(p.referenceTokens)-1
 
124
 
 
125
                rValue := reflect.ValueOf(node)
 
126
                kind = rValue.Kind()
 
127
 
 
128
                switch kind {
 
129
 
 
130
                case reflect.Map:
 
131
                        m := node.(map[string]interface{})
 
132
                        if _, ok := m[decodedToken]; ok {
 
133
                                node = m[decodedToken]
 
134
                                if isLastToken && i.mode == "SET" {
 
135
                                        m[decodedToken] = i.setInValue
 
136
                                }
 
137
                        } else {
 
138
                                i.outError = errors.New(fmt.Sprintf("Object has no key '%s'", token))
 
139
                                i.getOutKind = kind
 
140
                                i.getOutNode = nil
 
141
                                return
 
142
                        }
 
143
 
 
144
                case reflect.Slice:
 
145
                        s := node.([]interface{})
 
146
                        tokenIndex, err := strconv.Atoi(token)
 
147
                        if err != nil {
 
148
                                i.outError = errors.New(fmt.Sprintf("Invalid array index '%s'", token))
 
149
                                i.getOutKind = kind
 
150
                                i.getOutNode = nil
 
151
                                return
 
152
                        }
 
153
                        sLength := len(s)
 
154
                        if tokenIndex < 0 || tokenIndex >= sLength {
 
155
                                i.outError = errors.New(fmt.Sprintf("Out of bound array[0,%d] index '%d'", sLength, tokenIndex))
 
156
                                i.getOutKind = kind
 
157
                                i.getOutNode = nil
 
158
                                return
 
159
                        }
 
160
 
 
161
                        node = s[tokenIndex]
 
162
                        if isLastToken && i.mode == "SET" {
 
163
                                s[tokenIndex] = i.setInValue
 
164
                        }
 
165
 
 
166
                default:
 
167
                        i.outError = errors.New(fmt.Sprintf("Invalid token reference '%s'", token))
 
168
                        i.getOutKind = kind
 
169
                        i.getOutNode = nil
 
170
                        return
 
171
                }
 
172
 
 
173
        }
 
174
 
 
175
        rValue := reflect.ValueOf(node)
 
176
        kind = rValue.Kind()
 
177
 
 
178
        i.getOutNode = node
 
179
        i.getOutKind = kind
 
180
        i.outError = nil
 
181
}
 
182
 
 
183
// Pointer to string representation function
 
184
func (p *JsonPointer) String() string {
 
185
 
 
186
        if len(p.referenceTokens) == 0 {
 
187
                return const_empty_pointer
 
188
        }
 
189
 
 
190
        pointerString := const_pointer_separator + strings.Join(p.referenceTokens, const_pointer_separator)
 
191
 
 
192
        return pointerString
 
193
}
 
194
 
 
195
// Specific JSON pointer encoding here
 
196
// ~0 => ~
 
197
// ~1 => /
 
198
// ... and vice versa
 
199
 
 
200
const (
 
201
        const_encoded_reference_token_0 = `~0`
 
202
        const_encoded_reference_token_1 = `~1`
 
203
        const_decoded_reference_token_0 = `~`
 
204
        const_decoded_reference_token_1 = `/`
 
205
)
 
206
 
 
207
func decodeReferenceToken(token string) string {
 
208
        step1 := strings.Replace(token, const_encoded_reference_token_1, const_decoded_reference_token_1, -1)
 
209
        step2 := strings.Replace(step1, const_encoded_reference_token_0, const_decoded_reference_token_0, -1)
 
210
        return step2
 
211
}
 
212
 
 
213
func encodeReferenceToken(token string) string {
 
214
        step1 := strings.Replace(token, const_decoded_reference_token_1, const_encoded_reference_token_1, -1)
 
215
        step2 := strings.Replace(step1, const_decoded_reference_token_0, const_encoded_reference_token_0, -1)
 
216
        return step2
 
217
}