func DeepGet(data interface{}, path []string, fallback interface{}) interface{} { current := data for i := 0; i < len(path); i++ { part := path[i] switch current.(type) { // arrays case []interface{}: currentAsArray := current.([]interface{}) if stringutil.IsInteger(part) { if partIndex, err := strconv.Atoi(part); err == nil { if partIndex < len(currentAsArray) { if value := currentAsArray[partIndex]; value != nil { current = value continue } } } } return fallback // maps case map[string]interface{}: currentAsMap := current.(map[string]interface{}) if value, ok := currentAsMap[part]; !ok { return fallback } else { current = value } } } return current }
func DeepSet(data interface{}, path []string, value interface{}) interface{} { if len(path) == 0 { return data } var first = path[0] var rest = make([]string, 0) if len(path) > 1 { rest = path[1:] } // Leaf Nodes // this is where the value we're setting actually gets set/appended if len(rest) == 0 { switch data.(type) { // parent element is an ARRAY case []interface{}: return append(data.([]interface{}), value) // parent element is a MAP case map[string]interface{}: dataMap := data.(map[string]interface{}) dataMap[first] = value return dataMap } } else { // Array Embedding // this is where keys that are actually array indices get processed // ================================ // is `first' numeric (an array index) if stringutil.IsInteger(rest[0]) { switch data.(type) { case map[string]interface{}: dataMap := data.(map[string]interface{}) // is the value at `first' in the map isn't present or isn't an array, create it // --------> curVal, _ := dataMap[first] switch curVal.(type) { case []interface{}: default: dataMap[first] = make([]interface{}, 0) curVal, _ = dataMap[first] } // <--------| // recurse into our cool array and do awesome stuff with it dataMap[first] = DeepSet(curVal.([]interface{}), rest, value).([]interface{}) return dataMap default: // log.Printf("WHAT %s/%s", first, rest) } // Intermediate Map Processing // this is where branch nodes get created and populated via recursion // depending on the data type of the input `data', non-existent maps // will be created and either set to `data[first]' (the map) // or appended to `data[first]' (the array) // ================================ } else { switch data.(type) { // handle arrays of maps case []interface{}: dataArray := data.([]interface{}) if curIndex, err := strconv.Atoi(first); err == nil { if curIndex >= len(dataArray) { for add := len(dataArray); add <= curIndex; add++ { dataArray = append(dataArray, make(map[string]interface{})) } } if curIndex < len(dataArray) { dataArray[curIndex] = DeepSet(dataArray[curIndex], rest, value) return dataArray } } // handle good old fashioned maps-of-maps case map[string]interface{}: dataMap := data.(map[string]interface{}) // is the value at `first' in the map isn't present or isn't a map, create it // --------> curVal, _ := dataMap[first] switch curVal.(type) { case map[string]interface{}: default: dataMap[first] = make(map[string]interface{}) curVal, _ = dataMap[first] } // <--------| dataMap[first] = DeepSet(dataMap[first], rest, value) return dataMap } } } return data }