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