newmap.go raw

   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  
  18  package mxj
  19  
  20  import (
  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.
  38  func (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
  96  func 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