| // Copyright 2015 xeipuuv ( https://github.com/xeipuuv ) | |
| // | |
| // Licensed under the Apache License, Version 2.0 (the "License"); | |
| // you may not use this file except in compliance with the License. | |
| // You may obtain a copy of the License at | |
| // | |
| // http://www.apache.org/licenses/LICENSE-2.0 | |
| // | |
| // Unless required by applicable law or agreed to in writing, software | |
| // distributed under the License is distributed on an "AS IS" BASIS, | |
| // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
| // See the License for the specific language governing permissions and | |
| // limitations under the License. | |
| // author xeipuuv | |
| // author-github https://github.com/xeipuuv | |
| // author-mail xeipuuv@gmail.com | |
| // | |
| // repository-name gojsonpointer | |
| // repository-desc An implementation of JSON Pointer - Go language | |
| // | |
| // description Main and unique file. | |
| // | |
| // created 25-02-2013 | |
| package gojsonpointer | |
| import ( | |
| "errors" | |
| "fmt" | |
| "reflect" | |
| "strconv" | |
| "strings" | |
| ) | |
| const ( | |
| const_empty_pointer = `` | |
| const_pointer_separator = `/` | |
| const_invalid_start = `JSON pointer must be empty or start with a "` + const_pointer_separator + `"` | |
| ) | |
| type implStruct struct { | |
| mode string // "SET" or "GET" | |
| inDocument interface{} | |
| setInValue interface{} | |
| getOutNode interface{} | |
| getOutKind reflect.Kind | |
| outError error | |
| } | |
| type JsonPointer struct { | |
| referenceTokens []string | |
| } | |
| // NewJsonPointer parses the given string JSON pointer and returns an object | |
| func NewJsonPointer(jsonPointerString string) (p JsonPointer, err error) { | |
| // Pointer to the root of the document | |
| if len(jsonPointerString) == 0 { | |
| // Keep referenceTokens nil | |
| return | |
| } | |
| if jsonPointerString[0] != '/' { | |
| return p, errors.New(const_invalid_start) | |
| } | |
| p.referenceTokens = strings.Split(jsonPointerString[1:], const_pointer_separator) | |
| return | |
| } | |
| // Uses the pointer to retrieve a value from a JSON document | |
| func (p *JsonPointer) Get(document interface{}) (interface{}, reflect.Kind, error) { | |
| is := &implStruct{mode: "GET", inDocument: document} | |
| p.implementation(is) | |
| return is.getOutNode, is.getOutKind, is.outError | |
| } | |
| // Uses the pointer to update a value from a JSON document | |
| func (p *JsonPointer) Set(document interface{}, value interface{}) (interface{}, error) { | |
| is := &implStruct{mode: "SET", inDocument: document, setInValue: value} | |
| p.implementation(is) | |
| return document, is.outError | |
| } | |
| // Both Get and Set functions use the same implementation to avoid code duplication | |
| func (p *JsonPointer) implementation(i *implStruct) { | |
| kind := reflect.Invalid | |
| // Full document when empty | |
| if len(p.referenceTokens) == 0 { | |
| i.getOutNode = i.inDocument | |
| i.outError = nil | |
| i.getOutKind = kind | |
| i.outError = nil | |
| return | |
| } | |
| node := i.inDocument | |
| for ti, token := range p.referenceTokens { | |
| isLastToken := ti == len(p.referenceTokens)-1 | |
| switch v := node.(type) { | |
| case map[string]interface{}: | |
| decodedToken := decodeReferenceToken(token) | |
| if _, ok := v[decodedToken]; ok { | |
| node = v[decodedToken] | |
| if isLastToken && i.mode == "SET" { | |
| v[decodedToken] = i.setInValue | |
| } | |
| } else { | |
| i.outError = fmt.Errorf("Object has no key '%s'", decodedToken) | |
| i.getOutKind = reflect.Map | |
| i.getOutNode = nil | |
| return | |
| } | |
| case []interface{}: | |
| tokenIndex, err := strconv.Atoi(token) | |
| if err != nil { | |
| i.outError = fmt.Errorf("Invalid array index '%s'", token) | |
| i.getOutKind = reflect.Slice | |
| i.getOutNode = nil | |
| return | |
| } | |
| if tokenIndex < 0 || tokenIndex >= len(v) { | |
| i.outError = fmt.Errorf("Out of bound array[0,%d] index '%d'", len(v), tokenIndex) | |
| i.getOutKind = reflect.Slice | |
| i.getOutNode = nil | |
| return | |
| } | |
| node = v[tokenIndex] | |
| if isLastToken && i.mode == "SET" { | |
| v[tokenIndex] = i.setInValue | |
| } | |
| default: | |
| i.outError = fmt.Errorf("Invalid token reference '%s'", token) | |
| i.getOutKind = reflect.ValueOf(node).Kind() | |
| i.getOutNode = nil | |
| return | |
| } | |
| } | |
| i.getOutNode = node | |
| i.getOutKind = reflect.ValueOf(node).Kind() | |
| i.outError = nil | |
| } | |
| // Pointer to string representation function | |
| func (p *JsonPointer) String() string { | |
| if len(p.referenceTokens) == 0 { | |
| return const_empty_pointer | |
| } | |
| pointerString := const_pointer_separator + strings.Join(p.referenceTokens, const_pointer_separator) | |
| return pointerString | |
| } | |
| // Specific JSON pointer encoding here | |
| // ~0 => ~ | |
| // ~1 => / | |
| // ... and vice versa | |
| func decodeReferenceToken(token string) string { | |
| step1 := strings.Replace(token, `~1`, `/`, -1) | |
| step2 := strings.Replace(step1, `~0`, `~`, -1) | |
| return step2 | |
| } | |
| func encodeReferenceToken(token string) string { | |
| step1 := strings.Replace(token, `~`, `~0`, -1) | |
| step2 := strings.Replace(step1, `/`, `~1`, -1) | |
| return step2 | |
| } |