updatevalues.go 7.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249
  1. // Copyright 2012-2014 Charles Banning. All rights reserved.
  2. // Use of this source code is governed by a BSD-style
  3. // license that can be found in the LICENSE file
  4. // updatevalues.go - modify a value based on path and possibly sub-keys
  5. package mxj
  6. import (
  7. "fmt"
  8. "strconv"
  9. "strings"
  10. )
  11. // Update value based on path and possible sub-key values.
  12. // A count of the number of values changed and any error are returned.
  13. // If the count == 0, then no path (and subkeys) matched.
  14. // 'newVal' can be a Map or map[string]interface{} value with a single 'key' that is the key to be modified
  15. // or a string value "key:value[:type]" where type is "bool" or "num" to cast the value.
  16. // 'path' is dot-notation list of keys to traverse; last key in path can be newVal key
  17. // NOTE: 'path' spec does not currently support indexed array references.
  18. // 'subkeys' are "key:value[:type]" entries that must match for path node
  19. // The subkey can be wildcarded - "key:*" - to require that it's there with some value.
  20. // If a subkey is preceeded with the '!' character, the key:value[:type] entry is treated as an
  21. // exclusion critera - e.g., "!author:William T. Gaddis".
  22. func (mv Map) UpdateValuesForPath(newVal interface{}, path string, subkeys ...string) (int, error) {
  23. m := map[string]interface{}(mv)
  24. // extract the subkeys
  25. var subKeyMap map[string]interface{}
  26. if len(subkeys) > 0 {
  27. var err error
  28. subKeyMap, err = getSubKeyMap(subkeys...)
  29. if err != nil {
  30. return 0, err
  31. }
  32. }
  33. // extract key and value from newVal
  34. var key string
  35. var val interface{}
  36. switch newVal.(type) {
  37. case map[string]interface{}, Map:
  38. switch newVal.(type) { // "fallthrough is not permitted in type switch" (Spec)
  39. case Map:
  40. newVal = newVal.(Map).Old()
  41. }
  42. if len(newVal.(map[string]interface{})) != 1 {
  43. return 0, fmt.Errorf("newVal map can only have len == 1 - %+v", newVal)
  44. }
  45. for key, val = range newVal.(map[string]interface{}) {
  46. }
  47. case string: // split it as a key:value pair
  48. ss := strings.Split(newVal.(string), ":")
  49. n := len(ss)
  50. if n < 2 || n > 3 {
  51. return 0, fmt.Errorf("unknown newVal spec - %+v", newVal)
  52. }
  53. key = ss[0]
  54. if n == 2 {
  55. val = interface{}(ss[1])
  56. } else if n == 3 {
  57. switch ss[2] {
  58. case "bool", "boolean":
  59. nv, err := strconv.ParseBool(ss[1])
  60. if err != nil {
  61. return 0, fmt.Errorf("can't convert newVal to bool - %+v", newVal)
  62. }
  63. val = interface{}(nv)
  64. case "num", "numeric", "float", "int":
  65. nv, err := strconv.ParseFloat(ss[1], 64)
  66. if err != nil {
  67. return 0, fmt.Errorf("can't convert newVal to float64 - %+v", newVal)
  68. }
  69. val = interface{}(nv)
  70. default:
  71. return 0, fmt.Errorf("unknown type for newVal value - %+v", newVal)
  72. }
  73. }
  74. default:
  75. return 0, fmt.Errorf("invalid newVal type - %+v", newVal)
  76. }
  77. // parse path
  78. keys := strings.Split(path, ".")
  79. var count int
  80. updateValuesForKeyPath(key, val, m, keys, subKeyMap, &count)
  81. return count, nil
  82. }
  83. // navigate the path
  84. func updateValuesForKeyPath(key string, value interface{}, m interface{}, keys []string, subkeys map[string]interface{}, cnt *int) {
  85. // ----- at end node: looking at possible node to get 'key' ----
  86. if len(keys) == 1 {
  87. updateValue(key, value, m, keys[0], subkeys, cnt)
  88. return
  89. }
  90. // ----- here we are navigating the path thru the penultimate node --------
  91. // key of interest is keys[0] - the next in the path
  92. switch keys[0] {
  93. case "*": // wildcard - scan all values
  94. switch m.(type) {
  95. case map[string]interface{}:
  96. for _, v := range m.(map[string]interface{}) {
  97. updateValuesForKeyPath(key, value, v, keys[1:], subkeys, cnt)
  98. }
  99. case []interface{}:
  100. for _, v := range m.([]interface{}) {
  101. switch v.(type) {
  102. // flatten out a list of maps - keys are processed
  103. case map[string]interface{}:
  104. for _, vv := range v.(map[string]interface{}) {
  105. updateValuesForKeyPath(key, value, vv, keys[1:], subkeys, cnt)
  106. }
  107. default:
  108. updateValuesForKeyPath(key, value, v, keys[1:], subkeys, cnt)
  109. }
  110. }
  111. }
  112. default: // key - must be map[string]interface{}
  113. switch m.(type) {
  114. case map[string]interface{}:
  115. if v, ok := m.(map[string]interface{})[keys[0]]; ok {
  116. updateValuesForKeyPath(key, value, v, keys[1:], subkeys, cnt)
  117. }
  118. case []interface{}: // may be buried in list
  119. for _, v := range m.([]interface{}) {
  120. switch v.(type) {
  121. case map[string]interface{}:
  122. if vv, ok := v.(map[string]interface{})[keys[0]]; ok {
  123. updateValuesForKeyPath(key, value, vv, keys[1:], subkeys, cnt)
  124. }
  125. }
  126. }
  127. }
  128. }
  129. }
  130. // change value if key and subkeys are present
  131. func updateValue(key string, value interface{}, m interface{}, keys0 string, subkeys map[string]interface{}, cnt *int) {
  132. // there are two possible options for the value of 'keys0': map[string]interface, []interface{}
  133. // and 'key' is a key in the map or is a key in a map in a list.
  134. switch m.(type) {
  135. case map[string]interface{}: // gotta have the last key
  136. if keys0 == "*" {
  137. for k := range m.(map[string]interface{}) {
  138. updateValue(key, value, m, k, subkeys, cnt)
  139. }
  140. return
  141. }
  142. endVal, _ := m.(map[string]interface{})[keys0]
  143. // if newV key is the end of path, replace the value for path-end
  144. // may be []interface{} - means replace just an entry w/ subkeys
  145. // otherwise replace the keys0 value if subkeys are there
  146. // NOTE: this will replace the subkeys, also
  147. if key == keys0 {
  148. switch endVal.(type) {
  149. case map[string]interface{}:
  150. if ok := hasSubKeys(m, subkeys); ok {
  151. (m.(map[string]interface{}))[keys0] = value
  152. (*cnt)++
  153. }
  154. case []interface{}:
  155. // without subkeys can't select list member to modify
  156. // so key:value spec is it ...
  157. if len(subkeys) == 0 {
  158. (m.(map[string]interface{}))[keys0] = value
  159. (*cnt)++
  160. break
  161. }
  162. nv := make([]interface{}, 0)
  163. var valmodified bool
  164. for _, v := range endVal.([]interface{}) {
  165. // check entry subkeys
  166. if ok := hasSubKeys(v, subkeys); ok {
  167. // replace v with value
  168. nv = append(nv, value)
  169. valmodified = true
  170. (*cnt)++
  171. continue
  172. }
  173. nv = append(nv, v)
  174. }
  175. if valmodified {
  176. (m.(map[string]interface{}))[keys0] = interface{}(nv)
  177. }
  178. default: // anything else is a strict replacement
  179. if len(subkeys) == 0 {
  180. (m.(map[string]interface{}))[keys0] = value
  181. (*cnt)++
  182. }
  183. }
  184. return
  185. }
  186. // so value is for an element of endVal
  187. // if endVal is a map then 'key' must be there w/ subkeys
  188. // if endVal is a list then 'key' must be in a list member w/ subkeys
  189. switch endVal.(type) {
  190. case map[string]interface{}:
  191. if ok := hasSubKeys(endVal, subkeys); !ok {
  192. return
  193. }
  194. if _, ok := (endVal.(map[string]interface{}))[key]; ok {
  195. (endVal.(map[string]interface{}))[key] = value
  196. (*cnt)++
  197. }
  198. case []interface{}: // keys0 points to a list, check subkeys
  199. for _, v := range endVal.([]interface{}) {
  200. // got to be a map so we can replace value for 'key'
  201. vv, vok := v.(map[string]interface{})
  202. if !vok {
  203. continue
  204. }
  205. if _, ok := vv[key]; !ok {
  206. continue
  207. }
  208. if !hasSubKeys(vv, subkeys) {
  209. continue
  210. }
  211. vv[key] = value
  212. (*cnt)++
  213. }
  214. }
  215. case []interface{}: // key may be in a list member
  216. // don't need to handle keys0 == "*"; we're looking at everything, anyway.
  217. for _, v := range m.([]interface{}) {
  218. // only map values - we're looking for 'key'
  219. mm, ok := v.(map[string]interface{})
  220. if !ok {
  221. continue
  222. }
  223. if _, ok := mm[key]; !ok {
  224. continue
  225. }
  226. if !hasSubKeys(mm, subkeys) {
  227. continue
  228. }
  229. mm[key] = value
  230. (*cnt)++
  231. }
  232. }
  233. // return
  234. }