...

Source file src/github.com/xeipuuv/gojsonpointer/pointer.go

Documentation: github.com/xeipuuv/gojsonpointer

     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  type JsonPointer struct {
    56  	referenceTokens []string
    57  }
    58  
    59  // NewJsonPointer parses the given string JSON pointer and returns an object
    60  func NewJsonPointer(jsonPointerString string) (p JsonPointer, err error) {
    61  
    62  	// Pointer to the root of the document
    63  	if len(jsonPointerString) == 0 {
    64  		// Keep referenceTokens nil
    65  		return
    66  	}
    67  	if jsonPointerString[0] != '/' {
    68  		return p, errors.New(const_invalid_start)
    69  	}
    70  
    71  	p.referenceTokens = strings.Split(jsonPointerString[1:], const_pointer_separator)
    72  	return
    73  }
    74  
    75  // Uses the pointer to retrieve a value from a JSON document
    76  func (p *JsonPointer) Get(document interface{}) (interface{}, reflect.Kind, error) {
    77  
    78  	is := &implStruct{mode: "GET", inDocument: document}
    79  	p.implementation(is)
    80  	return is.getOutNode, is.getOutKind, is.outError
    81  
    82  }
    83  
    84  // Uses the pointer to update a value from a JSON document
    85  func (p *JsonPointer) Set(document interface{}, value interface{}) (interface{}, error) {
    86  
    87  	is := &implStruct{mode: "SET", inDocument: document, setInValue: value}
    88  	p.implementation(is)
    89  	return document, is.outError
    90  
    91  }
    92  
    93  // Uses the pointer to delete a value from a JSON document
    94  func (p *JsonPointer) Delete(document interface{}) (interface{}, error) {
    95  	is := &implStruct{mode: "DEL", inDocument: document}
    96  	p.implementation(is)
    97  	return document, is.outError
    98  }
    99  
   100  // Both Get and Set functions use the same implementation to avoid code duplication
   101  func (p *JsonPointer) implementation(i *implStruct) {
   102  
   103  	kind := reflect.Invalid
   104  
   105  	// Full document when empty
   106  	if len(p.referenceTokens) == 0 {
   107  		i.getOutNode = i.inDocument
   108  		i.outError = nil
   109  		i.getOutKind = kind
   110  		i.outError = nil
   111  		return
   112  	}
   113  
   114  	node := i.inDocument
   115  
   116  	previousNodes := make([]interface{}, len(p.referenceTokens))
   117  	previousTokens := make([]string, len(p.referenceTokens))
   118  
   119  	for ti, token := range p.referenceTokens {
   120  
   121  		isLastToken := ti == len(p.referenceTokens)-1
   122  		previousNodes[ti] = node
   123  		previousTokens[ti] = token
   124  
   125  		switch v := node.(type) {
   126  
   127  		case map[string]interface{}:
   128  			decodedToken := decodeReferenceToken(token)
   129  			if _, ok := v[decodedToken]; ok {
   130  				node = v[decodedToken]
   131  				if isLastToken && i.mode == "SET" {
   132  					v[decodedToken] = i.setInValue
   133  				} else if isLastToken && i.mode == "DEL" {
   134  					delete(v, decodedToken)
   135  				}
   136  			} else if isLastToken && i.mode == "SET" {
   137  				v[decodedToken] = i.setInValue
   138  			} else {
   139  				i.outError = fmt.Errorf("Object has no key '%s'", decodedToken)
   140  				i.getOutKind = reflect.Map
   141  				i.getOutNode = nil
   142  				return
   143  			}
   144  
   145  		case []interface{}:
   146  			tokenIndex, err := strconv.Atoi(token)
   147  			if err != nil {
   148  				i.outError = fmt.Errorf("Invalid array index '%s'", token)
   149  				i.getOutKind = reflect.Slice
   150  				i.getOutNode = nil
   151  				return
   152  			}
   153  			if tokenIndex < 0 || tokenIndex >= len(v) {
   154  				i.outError = fmt.Errorf("Out of bound array[0,%d] index '%d'", len(v), tokenIndex)
   155  				i.getOutKind = reflect.Slice
   156  				i.getOutNode = nil
   157  				return
   158  			}
   159  
   160  			node = v[tokenIndex]
   161  			if isLastToken && i.mode == "SET" {
   162  				v[tokenIndex] = i.setInValue
   163  			} else if isLastToken && i.mode == "DEL" {
   164  				v[tokenIndex] = v[len(v)-1]
   165  				v[len(v)-1] = nil
   166  				v = v[:len(v)-1]
   167  				previousNodes[ti-1].(map[string]interface{})[previousTokens[ti-1]] = v
   168  			}
   169  
   170  		default:
   171  			i.outError = fmt.Errorf("Invalid token reference '%s'", token)
   172  			i.getOutKind = reflect.ValueOf(node).Kind()
   173  			i.getOutNode = nil
   174  			return
   175  		}
   176  
   177  	}
   178  
   179  	i.getOutNode = node
   180  	i.getOutKind = reflect.ValueOf(node).Kind()
   181  	i.outError = nil
   182  }
   183  
   184  // Pointer to string representation function
   185  func (p *JsonPointer) String() string {
   186  
   187  	if len(p.referenceTokens) == 0 {
   188  		return const_empty_pointer
   189  	}
   190  
   191  	pointerString := const_pointer_separator + strings.Join(p.referenceTokens, const_pointer_separator)
   192  
   193  	return pointerString
   194  }
   195  
   196  // Specific JSON pointer encoding here
   197  // ~0 => ~
   198  // ~1 => /
   199  // ... and vice versa
   200  
   201  func decodeReferenceToken(token string) string {
   202  	step1 := strings.Replace(token, `~1`, `/`, -1)
   203  	step2 := strings.Replace(step1, `~0`, `~`, -1)
   204  	return step2
   205  }
   206  
   207  func encodeReferenceToken(token string) string {
   208  	step1 := strings.Replace(token, `~`, `~0`, -1)
   209  	step2 := strings.Replace(step1, `/`, `~1`, -1)
   210  	return step2
   211  }
   212  

View as plain text