Expandable structure enable nested data structure to substitution source for other data structure or text. Data substitution expression starts with $ sign, you can use path where dot or [index] allows access data sub node.
ExpandAsText expands any expression that has satisfied dependencies, meaning only expression that path is present can be expanded, otherwise expression is left unchanged. In case when UDF is used, expression is expanded if UDF does not return an error.
Usage:
aMap := Map(map[string]interface{}{
"key1": 1,
"key2": map[string]interface{}{
"subKey1":10,
"subKey2":20,
},
"key3": "subKey2",
"array": []interface{}{
111, 222, 333,
},
"slice": []interface{}{
map[string]interface{}{
"attr1":111,
"attr2":222,
},
},
})
expandedText := aMap.ExpandAsText(`1: $key1,
2: ${array[2]}
3: $key2.subKey1
4: $key2[$key3] ${slice[0].attr1}
5: ${(key1 + 1) * 3}
6: $abc
7: end
`)
/* expands to
1: 1,
2: 333
3: 10
4: 20 111
5: 6
6: $abc
7: end
*/
aMap := Map(map[string]interface{}{
"key1": 1,
"key2": map[string]interface{}{
"subKey1":10,
"subKey2":20,
},
"key3": "subKey2",
"array": []interface{}{
111, 222, 333,
},
"slice": []interface{}{
map[string]interface{}{
"attr1":111,
"attr2":222,
},
},
})
data := map[string]interface{}{
"k1": "$key1",
"k2":"$array",
"k3":"$key2",
}
expanded := aMap.Expand(data)
/* expands to
map[string]interface{}{
"k1": 1,
"k2": []interface{}{111, 222, 333},
"k3": map[string]interface{}{
"subKey1":10,
"subKey2":20,
},
}
*/
You can add dynamic data substitution by registering function in top level map.
type Udf func(interface{}, Map) (interface{}, error)
aMap: data.Map(map[string]interface{}{
"dailyCap": 100,
"overallCap": 2,
"AsFloat": func(source interface{}, state Map) (interface{}, error) {
return toolbox.AsFloat(source), nil
},
})
expanded := aMap.Expand("$AsFloat($dailyCap)")
//expands to actual float: 100.0
Using a generic data structure in a form []map[string]interface{} is extremely memory inefficient, CompactedSlice addresses the memory inefficiency by storing the new item values in a slice and by mapping corresponding fields to the item slice index positions. On top of that any neighboring nil values can be compacted too.
Usage
collection := NewCompactedSlice(true, true)
for i := 0;i<10;i++ {
collection.Add(map[string]interface{}{
"f1": i+1,
"f12": i+10,
"f15": i*20,
"f20": i+4,
"f11": nil,
"f12": nil,
"f13": nil,
"f14": "",
})
}
collection.Range(func(data interface{}) (bool, error) {
actual = append(actual, toolbox.AsMap(data))
return true, nil
})