pointer.go 5.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211
  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. // author xeipuuv
  15. // author-github https://github.com/xeipuuv
  16. // author-mail xeipuuv@gmail.com
  17. //
  18. // repository-name gojsonpointer
  19. // repository-desc An implementation of JSON Pointer - Go language
  20. //
  21. // description Main and unique file.
  22. //
  23. // created 25-02-2013
  24. package gojsonpointer
  25. import (
  26. "errors"
  27. "fmt"
  28. "reflect"
  29. "strconv"
  30. "strings"
  31. )
  32. const (
  33. const_empty_pointer = ``
  34. const_pointer_separator = `/`
  35. const_invalid_start = `JSON pointer must be empty or start with a "` + const_pointer_separator + `"`
  36. )
  37. type implStruct struct {
  38. mode string // "SET" or "GET"
  39. inDocument interface{}
  40. setInValue interface{}
  41. getOutNode interface{}
  42. getOutKind reflect.Kind
  43. outError error
  44. }
  45. type JsonPointer struct {
  46. referenceTokens []string
  47. }
  48. // NewJsonPointer parses the given string JSON pointer and returns an object
  49. func NewJsonPointer(jsonPointerString string) (p JsonPointer, err error) {
  50. // Pointer to the root of the document
  51. if len(jsonPointerString) == 0 {
  52. // Keep referenceTokens nil
  53. return
  54. }
  55. if jsonPointerString[0] != '/' {
  56. return p, errors.New(const_invalid_start)
  57. }
  58. p.referenceTokens = strings.Split(jsonPointerString[1:], const_pointer_separator)
  59. return
  60. }
  61. // Uses the pointer to retrieve a value from a JSON document
  62. func (p *JsonPointer) Get(document interface{}) (interface{}, reflect.Kind, error) {
  63. is := &implStruct{mode: "GET", inDocument: document}
  64. p.implementation(is)
  65. return is.getOutNode, is.getOutKind, is.outError
  66. }
  67. // Uses the pointer to update a value from a JSON document
  68. func (p *JsonPointer) Set(document interface{}, value interface{}) (interface{}, error) {
  69. is := &implStruct{mode: "SET", inDocument: document, setInValue: value}
  70. p.implementation(is)
  71. return document, is.outError
  72. }
  73. // Uses the pointer to delete a value from a JSON document
  74. func (p *JsonPointer) Delete(document interface{}) (interface{}, error) {
  75. is := &implStruct{mode: "DEL", inDocument: document}
  76. p.implementation(is)
  77. return document, is.outError
  78. }
  79. // Both Get and Set functions use the same implementation to avoid code duplication
  80. func (p *JsonPointer) implementation(i *implStruct) {
  81. kind := reflect.Invalid
  82. // Full document when empty
  83. if len(p.referenceTokens) == 0 {
  84. i.getOutNode = i.inDocument
  85. i.outError = nil
  86. i.getOutKind = kind
  87. i.outError = nil
  88. return
  89. }
  90. node := i.inDocument
  91. previousNodes := make([]interface{}, len(p.referenceTokens))
  92. previousTokens := make([]string, len(p.referenceTokens))
  93. for ti, token := range p.referenceTokens {
  94. isLastToken := ti == len(p.referenceTokens)-1
  95. previousNodes[ti] = node
  96. previousTokens[ti] = token
  97. switch v := node.(type) {
  98. case map[string]interface{}:
  99. decodedToken := decodeReferenceToken(token)
  100. if _, ok := v[decodedToken]; ok {
  101. node = v[decodedToken]
  102. if isLastToken && i.mode == "SET" {
  103. v[decodedToken] = i.setInValue
  104. } else if isLastToken && i.mode =="DEL" {
  105. delete(v,decodedToken)
  106. }
  107. } else if (isLastToken && i.mode == "SET") {
  108. v[decodedToken] = i.setInValue
  109. } else {
  110. i.outError = fmt.Errorf("Object has no key '%s'", decodedToken)
  111. i.getOutKind = reflect.Map
  112. i.getOutNode = nil
  113. return
  114. }
  115. case []interface{}:
  116. tokenIndex, err := strconv.Atoi(token)
  117. if err != nil {
  118. i.outError = fmt.Errorf("Invalid array index '%s'", token)
  119. i.getOutKind = reflect.Slice
  120. i.getOutNode = nil
  121. return
  122. }
  123. if tokenIndex < 0 || tokenIndex >= len(v) {
  124. i.outError = fmt.Errorf("Out of bound array[0,%d] index '%d'", len(v), tokenIndex)
  125. i.getOutKind = reflect.Slice
  126. i.getOutNode = nil
  127. return
  128. }
  129. node = v[tokenIndex]
  130. if isLastToken && i.mode == "SET" {
  131. v[tokenIndex] = i.setInValue
  132. } else if isLastToken && i.mode =="DEL" {
  133. v[tokenIndex] = v[len(v)-1]
  134. v[len(v)-1] = nil
  135. v = v[:len(v)-1]
  136. previousNodes[ti-1].(map[string]interface{})[previousTokens[ti-1]] = v
  137. }
  138. default:
  139. i.outError = fmt.Errorf("Invalid token reference '%s'", token)
  140. i.getOutKind = reflect.ValueOf(node).Kind()
  141. i.getOutNode = nil
  142. return
  143. }
  144. }
  145. i.getOutNode = node
  146. i.getOutKind = reflect.ValueOf(node).Kind()
  147. i.outError = nil
  148. }
  149. // Pointer to string representation function
  150. func (p *JsonPointer) String() string {
  151. if len(p.referenceTokens) == 0 {
  152. return const_empty_pointer
  153. }
  154. pointerString := const_pointer_separator + strings.Join(p.referenceTokens, const_pointer_separator)
  155. return pointerString
  156. }
  157. // Specific JSON pointer encoding here
  158. // ~0 => ~
  159. // ~1 => /
  160. // ... and vice versa
  161. func decodeReferenceToken(token string) string {
  162. step1 := strings.Replace(token, `~1`, `/`, -1)
  163. step2 := strings.Replace(step1, `~0`, `~`, -1)
  164. return step2
  165. }
  166. func encodeReferenceToken(token string) string {
  167. step1 := strings.Replace(token, `~`, `~0`, -1)
  168. step2 := strings.Replace(step1, `/`, `~1`, -1)
  169. return step2
  170. }