pointer.go 5.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218
  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 [email protected]
  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. func NewJsonPointer(jsonPointerString string) (JsonPointer, error) {
  46. var p JsonPointer
  47. err := p.parse(jsonPointerString)
  48. return p, err
  49. }
  50. type JsonPointer struct {
  51. referenceTokens []string
  52. }
  53. // "Constructor", parses the given string JSON pointer
  54. func (p *JsonPointer) parse(jsonPointerString string) error {
  55. var err error
  56. if jsonPointerString != const_empty_pointer {
  57. if !strings.HasPrefix(jsonPointerString, const_pointer_separator) {
  58. err = errors.New(const_invalid_start)
  59. } else {
  60. referenceTokens := strings.Split(jsonPointerString, const_pointer_separator)
  61. for _, referenceToken := range referenceTokens[1:] {
  62. p.referenceTokens = append(p.referenceTokens, referenceToken)
  63. }
  64. }
  65. }
  66. return err
  67. }
  68. // Uses the pointer to retrieve a value from a JSON document
  69. func (p *JsonPointer) Get(document interface{}) (interface{}, reflect.Kind, error) {
  70. is := &implStruct{mode: "GET", inDocument: document}
  71. p.implementation(is)
  72. return is.getOutNode, is.getOutKind, is.outError
  73. }
  74. // Uses the pointer to update a value from a JSON document
  75. func (p *JsonPointer) Set(document interface{}, value interface{}) (interface{}, error) {
  76. is := &implStruct{mode: "SET", inDocument: document, setInValue: value}
  77. p.implementation(is)
  78. return document, is.outError
  79. }
  80. // Both Get and Set functions use the same implementation to avoid code duplication
  81. func (p *JsonPointer) implementation(i *implStruct) {
  82. kind := reflect.Invalid
  83. // Full document when empty
  84. if len(p.referenceTokens) == 0 {
  85. i.getOutNode = i.inDocument
  86. i.outError = nil
  87. i.getOutKind = kind
  88. i.outError = nil
  89. return
  90. }
  91. node := i.inDocument
  92. for ti, token := range p.referenceTokens {
  93. decodedToken := decodeReferenceToken(token)
  94. isLastToken := ti == len(p.referenceTokens)-1
  95. rValue := reflect.ValueOf(node)
  96. kind = rValue.Kind()
  97. switch kind {
  98. case reflect.Map:
  99. m := node.(map[string]interface{})
  100. if _, ok := m[decodedToken]; ok {
  101. node = m[decodedToken]
  102. if isLastToken && i.mode == "SET" {
  103. m[decodedToken] = i.setInValue
  104. }
  105. } else {
  106. i.outError = errors.New(fmt.Sprintf("Object has no key '%s'", token))
  107. i.getOutKind = kind
  108. i.getOutNode = nil
  109. return
  110. }
  111. case reflect.Slice:
  112. s := node.([]interface{})
  113. tokenIndex, err := strconv.Atoi(token)
  114. if err != nil {
  115. i.outError = errors.New(fmt.Sprintf("Invalid array index '%s'", token))
  116. i.getOutKind = kind
  117. i.getOutNode = nil
  118. return
  119. }
  120. sLength := len(s)
  121. if tokenIndex < 0 || tokenIndex >= sLength {
  122. i.outError = errors.New(fmt.Sprintf("Out of bound array[0,%d] index '%d'", sLength, tokenIndex))
  123. i.getOutKind = kind
  124. i.getOutNode = nil
  125. return
  126. }
  127. node = s[tokenIndex]
  128. if isLastToken && i.mode == "SET" {
  129. s[tokenIndex] = i.setInValue
  130. }
  131. default:
  132. i.outError = errors.New(fmt.Sprintf("Invalid token reference '%s'", token))
  133. i.getOutKind = kind
  134. i.getOutNode = nil
  135. return
  136. }
  137. }
  138. rValue := reflect.ValueOf(node)
  139. kind = rValue.Kind()
  140. i.getOutNode = node
  141. i.getOutKind = kind
  142. i.outError = nil
  143. }
  144. // Pointer to string representation function
  145. func (p *JsonPointer) String() string {
  146. if len(p.referenceTokens) == 0 {
  147. return const_empty_pointer
  148. }
  149. pointerString := const_pointer_separator + strings.Join(p.referenceTokens, const_pointer_separator)
  150. return pointerString
  151. }
  152. // Specific JSON pointer encoding here
  153. // ~0 => ~
  154. // ~1 => /
  155. // ... and vice versa
  156. const (
  157. const_encoded_reference_token_0 = `~0`
  158. const_encoded_reference_token_1 = `~1`
  159. const_decoded_reference_token_0 = `~`
  160. const_decoded_reference_token_1 = `/`
  161. )
  162. func decodeReferenceToken(token string) string {
  163. step1 := strings.Replace(token, const_encoded_reference_token_1, const_decoded_reference_token_1, -1)
  164. step2 := strings.Replace(step1, const_encoded_reference_token_0, const_decoded_reference_token_0, -1)
  165. return step2
  166. }
  167. func encodeReferenceToken(token string) string {
  168. step1 := strings.Replace(token, const_decoded_reference_token_1, const_encoded_reference_token_1, -1)
  169. step2 := strings.Replace(step1, const_decoded_reference_token_0, const_encoded_reference_token_0, -1)
  170. return step2
  171. }