1 // Copyright 2015 xeipuuv ( https://github.com/xeipuuv )
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
7 // http://www.apache.org/licenses/LICENSE-2.0
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.
16 // author-github https://github.com/xeipuuv
17 // author-mail xeipuuv@gmail.com
19 // repository-name gojsonpointer
20 // repository-desc An implementation of JSON Pointer - Go language
22 // description Main and unique file.
37 const_empty_pointer = ``
38 const_pointer_separator = `/`
40 const_invalid_start = `JSON pointer must be empty or start with a "` + const_pointer_separator + `"`
43 type implStruct struct {
44 mode string // "SET" or "GET"
46 inDocument interface{}
48 setInValue interface{}
50 getOutNode interface{}
51 getOutKind reflect.Kind
55 func NewJsonPointer(jsonPointerString string) (JsonPointer, error) {
58 err := p.parse(jsonPointerString)
63 type JsonPointer struct {
64 referenceTokens []string
67 // "Constructor", parses the given string JSON pointer
68 func (p *JsonPointer) parse(jsonPointerString string) error {
72 if jsonPointerString != const_empty_pointer {
73 if !strings.HasPrefix(jsonPointerString, const_pointer_separator) {
74 err = errors.New(const_invalid_start)
76 referenceTokens := strings.Split(jsonPointerString, const_pointer_separator)
77 for _, referenceToken := range referenceTokens[1:] {
78 p.referenceTokens = append(p.referenceTokens, referenceToken)
86 // Uses the pointer to retrieve a value from a JSON document
87 func (p *JsonPointer) Get(document interface{}) (interface{}, reflect.Kind, error) {
89 is := &implStruct{mode: "GET", inDocument: document}
91 return is.getOutNode, is.getOutKind, is.outError
95 // Uses the pointer to update a value from a JSON document
96 func (p *JsonPointer) Set(document interface{}, value interface{}) (interface{}, error) {
98 is := &implStruct{mode: "SET", inDocument: document, setInValue: value}
100 return document, is.outError
104 // Both Get and Set functions use the same implementation to avoid code duplication
105 func (p *JsonPointer) implementation(i *implStruct) {
107 kind := reflect.Invalid
109 // Full document when empty
110 if len(p.referenceTokens) == 0 {
111 i.getOutNode = i.inDocument
120 for ti, token := range p.referenceTokens {
122 decodedToken := decodeReferenceToken(token)
123 isLastToken := ti == len(p.referenceTokens)-1
125 rValue := reflect.ValueOf(node)
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
138 i.outError = errors.New(fmt.Sprintf("Object has no key '%s'", token))
145 s := node.([]interface{})
146 tokenIndex, err := strconv.Atoi(token)
148 i.outError = errors.New(fmt.Sprintf("Invalid array index '%s'", token))
154 if tokenIndex < 0 || tokenIndex >= sLength {
155 i.outError = errors.New(fmt.Sprintf("Out of bound array[0,%d] index '%d'", sLength, tokenIndex))
162 if isLastToken && i.mode == "SET" {
163 s[tokenIndex] = i.setInValue
167 i.outError = errors.New(fmt.Sprintf("Invalid token reference '%s'", token))
175 rValue := reflect.ValueOf(node)
183 // Pointer to string representation function
184 func (p *JsonPointer) String() string {
186 if len(p.referenceTokens) == 0 {
187 return const_empty_pointer
190 pointerString := const_pointer_separator + strings.Join(p.referenceTokens, const_pointer_separator)
195 // Specific JSON pointer encoding here
198 // ... and vice versa
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 = `/`
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)
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)