newmap.go 5.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183
  1. // mxj - A collection of map[string]interface{} and associated XML and JSON utilities.
  2. // Copyright 2012-2014 Charles Banning. All rights reserved.
  3. // Use of this source code is governed by a BSD-style
  4. // license that can be found in the LICENSE file
  5. // remap.go - build a new Map from the current Map based on keyOld:keyNew mapppings
  6. // keys can use dot-notation, keyOld can use wildcard, '*'
  7. //
  8. // Computational strategy -
  9. // Using the key path - []string - traverse a new map[string]interface{} and
  10. // insert the oldVal as the newVal when we arrive at the end of the path.
  11. // If the type at the end is nil, then that is newVal
  12. // If the type at the end is a singleton (string, float64, bool) an array is created.
  13. // If the type at the end is an array, newVal is just appended.
  14. // If the type at the end is a map, it is inserted if possible or the map value
  15. // is converted into an array if necessary.
  16. package mxj
  17. import (
  18. "errors"
  19. "strings"
  20. )
  21. // (Map)NewMap - create a new Map from data in the current Map.
  22. // 'keypairs' are key mappings "oldKey:newKey" and specify that the current value of 'oldKey'
  23. // should be the value for 'newKey' in the returned Map.
  24. // - 'oldKey' supports dot-notation as described for (Map)ValuesForPath()
  25. // - 'newKey' supports dot-notation but with no wildcards, '*', or indexed arrays
  26. // - "oldKey" is shorthand for for the keypair value "oldKey:oldKey"
  27. // - "oldKey:" and ":newKey" are invalid keypair values
  28. // - if 'oldKey' does not exist in the current Map, it is not written to the new Map.
  29. // "null" is not supported unless it is the current Map.
  30. // - see newmap_test.go for several syntax examples
  31. //
  32. // NOTE: mv.NewMap() == mxj.New().
  33. func (mv Map) NewMap(keypairs ...string) (Map, error) {
  34. n := make(map[string]interface{}, 0)
  35. if len(keypairs) == 0 {
  36. return n, nil
  37. }
  38. // loop through the pairs
  39. var oldKey, newKey string
  40. var path []string
  41. for _, v := range keypairs {
  42. if len(v) == 0 {
  43. continue // just skip over empty keypair arguments
  44. }
  45. // initialize oldKey, newKey and check
  46. vv := strings.Split(v, ":")
  47. if len(vv) > 2 {
  48. return n, errors.New("oldKey:newKey keypair value not valid - " + v)
  49. }
  50. if len(vv) == 1 {
  51. oldKey, newKey = vv[0], vv[0]
  52. } else {
  53. oldKey, newKey = vv[0], vv[1]
  54. }
  55. strings.TrimSpace(oldKey)
  56. strings.TrimSpace(newKey)
  57. if i := strings.Index(newKey, "*"); i > -1 {
  58. return n, errors.New("newKey value cannot contain wildcard character - " + v)
  59. }
  60. if i := strings.Index(newKey, "["); i > -1 {
  61. return n, errors.New("newKey value cannot contain indexed arrays - " + v)
  62. }
  63. if oldKey == "" || newKey == "" {
  64. return n, errors.New("oldKey or newKey is not specified - " + v)
  65. }
  66. // get oldKey value
  67. oldVal, err := mv.ValuesForPath(oldKey)
  68. if err != nil {
  69. return n, err
  70. }
  71. if len(oldVal) == 0 {
  72. continue // oldKey has no value, may not exist in mv
  73. }
  74. // break down path
  75. path = strings.Split(newKey, ".")
  76. if path[len(path)-1] == "" { // ignore a trailing dot in newKey spec
  77. path = path[:len(path)-1]
  78. }
  79. addNewVal(&n, path, oldVal)
  80. }
  81. return n, nil
  82. }
  83. // navigate 'n' to end of path and add val
  84. func addNewVal(n *map[string]interface{}, path []string, val []interface{}) {
  85. // newVal - either singleton or array
  86. var newVal interface{}
  87. if len(val) == 1 {
  88. newVal = val[0] // is type interface{}
  89. } else {
  90. newVal = interface{}(val)
  91. }
  92. // walk to the position of interest, create it if necessary
  93. m := (*n) // initialize map walker
  94. var k string // key for m
  95. lp := len(path) - 1 // when to stop looking
  96. for i := 0; i < len(path); i++ {
  97. k = path[i]
  98. if i == lp {
  99. break
  100. }
  101. var nm map[string]interface{} // holds position of next-map
  102. switch m[k].(type) {
  103. case nil: // need a map for next node in path, so go there
  104. nm = make(map[string]interface{}, 0)
  105. m[k] = interface{}(nm)
  106. m = m[k].(map[string]interface{})
  107. case map[string]interface{}:
  108. // OK - got somewhere to walk to, go there
  109. m = m[k].(map[string]interface{})
  110. case []interface{}:
  111. // add a map and nm points to new map unless there's already
  112. // a map in the array, then nm points there
  113. // The placement of the next value in the array is dependent
  114. // on the sequence of members - could land on a map or a nil
  115. // value first. TODO: how to test this.
  116. a := make([]interface{}, 0)
  117. var foundmap bool
  118. for _, vv := range m[k].([]interface{}) {
  119. switch vv.(type) {
  120. case nil: // doesn't appear that this occurs, need a test case
  121. if foundmap { // use the first one in array
  122. a = append(a, vv)
  123. continue
  124. }
  125. nm = make(map[string]interface{}, 0)
  126. a = append(a, interface{}(nm))
  127. foundmap = true
  128. case map[string]interface{}:
  129. if foundmap { // use the first one in array
  130. a = append(a, vv)
  131. continue
  132. }
  133. nm = vv.(map[string]interface{})
  134. a = append(a, vv)
  135. foundmap = true
  136. default:
  137. a = append(a, vv)
  138. }
  139. }
  140. // no map found in array
  141. if !foundmap {
  142. nm = make(map[string]interface{}, 0)
  143. a = append(a, interface{}(nm))
  144. }
  145. m[k] = interface{}(a) // must insert in map
  146. m = nm
  147. default: // it's a string, float, bool, etc.
  148. aa := make([]interface{}, 0)
  149. nm = make(map[string]interface{}, 0)
  150. aa = append(aa, m[k], nm)
  151. m[k] = interface{}(aa)
  152. m = nm
  153. }
  154. }
  155. // value is nil, array or a singleton of some kind
  156. // initially m.(type) == map[string]interface{}
  157. v := m[k]
  158. switch v.(type) {
  159. case nil: // initialized
  160. m[k] = newVal
  161. case []interface{}:
  162. a := m[k].([]interface{})
  163. a = append(a, newVal)
  164. m[k] = interface{}(a)
  165. default: // v exists:string, float64, bool, map[string]interface, etc.
  166. a := make([]interface{}, 0)
  167. a = append(a, v, newVal)
  168. m[k] = interface{}(a)
  169. }
  170. }