newmap.go 5.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184
  1. // mxj - A collection of map[string]interface{} and associated XML and JSON utilities.
  2. // Copyright 2012-2014, 2018 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 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. // - mv.NewMap() == mxj.New()
  32. //
  33. // NOTE: "examples/partial.go" shows how to create arbitrary sub-docs of an XML doc.
  34. func (mv Map) NewMap(keypairs ...string) (Map, error) {
  35. n := make(map[string]interface{}, 0)
  36. if len(keypairs) == 0 {
  37. return n, nil
  38. }
  39. // loop through the pairs
  40. var oldKey, newKey string
  41. var path []string
  42. for _, v := range keypairs {
  43. if len(v) == 0 {
  44. continue // just skip over empty keypair arguments
  45. }
  46. // initialize oldKey, newKey and check
  47. vv := strings.Split(v, ":")
  48. if len(vv) > 2 {
  49. return n, errors.New("oldKey:newKey keypair value not valid - " + v)
  50. }
  51. if len(vv) == 1 {
  52. oldKey, newKey = vv[0], vv[0]
  53. } else {
  54. oldKey, newKey = vv[0], vv[1]
  55. }
  56. strings.TrimSpace(oldKey)
  57. strings.TrimSpace(newKey)
  58. if i := strings.Index(newKey, "*"); i > -1 {
  59. return n, errors.New("newKey value cannot contain wildcard character - " + v)
  60. }
  61. if i := strings.Index(newKey, "["); i > -1 {
  62. return n, errors.New("newKey value cannot contain indexed arrays - " + v)
  63. }
  64. if oldKey == "" || newKey == "" {
  65. return n, errors.New("oldKey or newKey is not specified - " + v)
  66. }
  67. // get oldKey value
  68. oldVal, err := mv.ValuesForPath(oldKey)
  69. if err != nil {
  70. return n, err
  71. }
  72. if len(oldVal) == 0 {
  73. continue // oldKey has no value, may not exist in mv
  74. }
  75. // break down path
  76. path = strings.Split(newKey, ".")
  77. if path[len(path)-1] == "" { // ignore a trailing dot in newKey spec
  78. path = path[:len(path)-1]
  79. }
  80. addNewVal(&n, path, oldVal)
  81. }
  82. return n, nil
  83. }
  84. // navigate 'n' to end of path and add val
  85. func addNewVal(n *map[string]interface{}, path []string, val []interface{}) {
  86. // newVal - either singleton or array
  87. var newVal interface{}
  88. if len(val) == 1 {
  89. newVal = val[0] // is type interface{}
  90. } else {
  91. newVal = interface{}(val)
  92. }
  93. // walk to the position of interest, create it if necessary
  94. m := (*n) // initialize map walker
  95. var k string // key for m
  96. lp := len(path) - 1 // when to stop looking
  97. for i := 0; i < len(path); i++ {
  98. k = path[i]
  99. if i == lp {
  100. break
  101. }
  102. var nm map[string]interface{} // holds position of next-map
  103. switch m[k].(type) {
  104. case nil: // need a map for next node in path, so go there
  105. nm = make(map[string]interface{}, 0)
  106. m[k] = interface{}(nm)
  107. m = m[k].(map[string]interface{})
  108. case map[string]interface{}:
  109. // OK - got somewhere to walk to, go there
  110. m = m[k].(map[string]interface{})
  111. case []interface{}:
  112. // add a map and nm points to new map unless there's already
  113. // a map in the array, then nm points there
  114. // The placement of the next value in the array is dependent
  115. // on the sequence of members - could land on a map or a nil
  116. // value first. TODO: how to test this.
  117. a := make([]interface{}, 0)
  118. var foundmap bool
  119. for _, vv := range m[k].([]interface{}) {
  120. switch vv.(type) {
  121. case nil: // doesn't appear that this occurs, need a test case
  122. if foundmap { // use the first one in array
  123. a = append(a, vv)
  124. continue
  125. }
  126. nm = make(map[string]interface{}, 0)
  127. a = append(a, interface{}(nm))
  128. foundmap = true
  129. case map[string]interface{}:
  130. if foundmap { // use the first one in array
  131. a = append(a, vv)
  132. continue
  133. }
  134. nm = vv.(map[string]interface{})
  135. a = append(a, vv)
  136. foundmap = true
  137. default:
  138. a = append(a, vv)
  139. }
  140. }
  141. // no map found in array
  142. if !foundmap {
  143. nm = make(map[string]interface{}, 0)
  144. a = append(a, interface{}(nm))
  145. }
  146. m[k] = interface{}(a) // must insert in map
  147. m = nm
  148. default: // it's a string, float, bool, etc.
  149. aa := make([]interface{}, 0)
  150. nm = make(map[string]interface{}, 0)
  151. aa = append(aa, m[k], nm)
  152. m[k] = interface{}(aa)
  153. m = nm
  154. }
  155. }
  156. // value is nil, array or a singleton of some kind
  157. // initially m.(type) == map[string]interface{}
  158. v := m[k]
  159. switch v.(type) {
  160. case nil: // initialized
  161. m[k] = newVal
  162. case []interface{}:
  163. a := m[k].([]interface{})
  164. a = append(a, newVal)
  165. m[k] = interface{}(a)
  166. default: // v exists:string, float64, bool, map[string]interface, etc.
  167. a := make([]interface{}, 0)
  168. a = append(a, v, newVal)
  169. m[k] = interface{}(a)
  170. }
  171. }