MergeDictArray
Each entry in an array is merged into the existing array if the keyed-value matches.
javascript · Filters And Transformers
Source
function is_object(o) { return o instanceof Object && !(o instanceof Array); } function merge_value(dst, src, key, conflict_strategy, overwrite_by_src, overwrite_by_dst) { if (overwrite_by_src.indexOf(key) >= 0) { return src; } else if (overwrite_by_dst.indexOf(key) >= 0) { return dst; } switch (conflict_strategy) { case 'destination': return dst; case 'source': return src; case 'merge': let dst_array = Array.isArray(dst) ? dst.concat() : (dst !== null ? [dst] : []); let src_array = Array.isArray(src) ? src : (src !== null ? [src] : []); src_array.forEach(v => { if (dst_array.indexOf(v) < 0) { dst_array.push(v); } }); if (dst_array.length == 1 && (!Array.isArray(src) || !Array.isArray(dst))) { return dst_array[0]; } else if (dst_array.length >= 1) { return dst_array; } return dst !== null ? dst : src; default: throw 'Invalid conflict_strategy: ' + conflict_strategy; } } function set_object_value(obj, path, value, append, conflict_strategy, overwrite_by_src, overwrite_by_dst) { const root = obj; path.split('.').forEach((key, i, keys) => { if (!key) { throw path + ' has an emtpy node.'; }else if (keys.length-1 === i) { if (!append || !(key in obj)) { obj[key] = value; } else { obj[key] = merge_value(obj[key], value, key, conflict_strategy, overwrite_by_src, overwrite_by_dst); } } else { if (!is_object(obj[key])) { obj[key] = {}; } obj = obj[key]; } }); return root; } function get_object_value(obj, path) { // this returns 'undefined' for undefined properties unlike dq(). for (const key of path.split('.')) { if (!is_object(obj)) { return undefined; } obj = obj[key]; } return obj; } function make_keymap(mapping) { let kmap = {}; mapping.split(',').map(x => x.trim()).forEach(x => { let vals = x.split(':'); if (vals.length < 2) { throw 'Invalid key:value pairs in the mapping.'; } let src_key = vals.shift().trim(); let dst_key = vals.join(':').trim(); kmap[dst_key] = src_key; }); return kmap; } function contains_value(dval, sval) { if (dval === undefined || sval === undefined) { return false; } sval = Array.isArray(sval) ? sval : [sval]; dval = Array.isArray(dval) ? dval : [dval]; return sval.filter(sv => dval.filter(dv => sv == dv).length > 0).length == sval.length; } function contains_object_value(dval, sval, kmap) { if (!is_object(dval) || !is_object(sval)) { return false; } for (const dk in kmap) { const sk = kmap[dk]; if (!contains_value(get_object_value(dval, dk), get_object_value(sval, sk))) { return false; } } return true; } function merge_object_value(dst, src, conflict_strategy, overwrite_by_src, overwrite_by_dst) { if (!is_object(dst)) { throw 'Invalid object in destination value: ' + JSON.stringify(dst); } if (!is_object(src)) { throw 'Invalid object in source value: ' + JSON.stringify(src); } let out = Object.assign({}, dst); for (const k in src) { if (k in dst) { for (const sval of Array.isArray(src[k]) ? src[k] : [src[k]]) { out[k] = merge_value(dst[k], sval, k, conflict_strategy, overwrite_by_src, overwrite_by_dst); } } else { out[k] = src[k]; } } return out; } function merge_array(dst, src, kmap, out_key, appendable, conflict_strategy, overwrite_by_src, overwrite_by_dst) { dst = dst.concat(); src.forEach(sval => { let merged = false; if (is_object(sval)) { dst.forEach((dval, index) => { if (contains_object_value(dval, sval, kmap)) { if (out_key) { dst[index] = set_object_value(dval, out_key, sval, true, conflict_strategy, overwrite_by_src, overwrite_by_dst); } else { dst[index] = merge_object_value(dval, sval, conflict_strategy, overwrite_by_src, overwrite_by_dst); } merged = true; } }); } if (!merged && appendable) { dst.push(out_key ? {[out_key]: sval} : sval); } }); return dst; } let dst_value = Array.isArray(args.value) ? args.value : []; const src_array = Array.isArray(args.merge_with) ? args.merge_with : [args.merge_with]; const key_map = make_keymap(args.mapping); const appendable = args.appendable.toLowerCase() == 'true'; const conflict_strategy = args.conflict_strategy ? args.conflict_strategy : 'merge'; const overwrite_by_src = argToList(args.overwrite_by_source); const overwrite_by_dst = argToList(args.overwrite_by_destination); if (overwrite_by_src.filter((v) => overwrite_by_dst.indexOf(v) >= 0).length !==0 ){ throw 'A key is conflict in overwrite_by_source and overwrite_by_destination.'; } if (src_array && args.merge_with !== null) { if (args.array_path) { dst_value.forEach(v => { let dst_array = dq(v, args.array_path); let new_array; if (!Array.isArray(dst_array)) { new_array = merge_array([dst_array], src_array, key_map, args.out_key, appendable, conflict_strategy, overwrite_by_src, overwrite_by_dst); if (new_array.length == 1) { new_array = new_array[0]; } } else { new_array = merge_array(dst_array, src_array, key_map, args.out_key, appendable, conflict_strategy, overwrite_by_src, overwrite_by_dst); } set_object_value(v, args.out_path ? args.out_path : args.array_path, new_array, false, conflict_strategy, overwrite_by_src, overwrite_by_dst); }); } else { dst_value = merge_array(dst_value, src_array, key_map, args.out_key, appendable, conflict_strategy, overwrite_by_src, overwrite_by_dst); if (args.out_path) { dst_value = [set_object_value({}, args.out_path, dst_value, false, conflict_strategy, overwrite_by_src, overwrite_by_dst)]; } } } return dst_value;
README
Each entry in an array is merged into the existing array if the keyed-value matches.
Script Data
| Name | Description |
|---|---|
| Script Type | javascript |
| Tags | transformer, general, entirelist |
| Cortex XSOAR Version | 6.0.0 |
Inputs
| Argument Name | Description |
|---|---|
| value | The input value |
| array_path | The relative path to the array from `value`. |
| merge_with | An array to merge from |
| mapping | A comma separated list of pairs [key of merge_with]:[key of value.array_path]. e.g. ip:dst_ip, hostname:dst_host |
| out_key | The key to the value where each of the destination value is to be set |
| out_path | The relative path to the destination array where the marged array is set |
| appendable | true if it allows to simply append source entries which doesn’t match, otherwise false |
| conflict_strategy | How to merge the values if the key conflicts. ‘source’ means data of ‘merge_with’, ‘destination’ means data of ‘array_path’. Default is ‘merge’. |
| overwrite_by_source | The comma separeted list of keys to overwrite by source value if the key conflicts. |
| overwrite_by_destination | The comma separeted list of keys to overwrite by destination value if the key conflicts. |
Outputs
There are no outputs for this script.
Example
!MergeDictArray value=`${.=[
{
"Address": "8.8.8.8",
"ANS": 15169,
"Hostname": "dns.google"
},
{
"Address": "8.8.4.4",
"ANS": 15169,
"Hostname": "dns.google"
}
]}` merge_with=`${.=[{
"Address": "8.8.8.8",
"ISP": "Google LLC",
"Longitude": -122.0775,
"Port": [
53
],
"CountryName": "United States",
"Latitude": 37.4056,
"Org": "Google LLC",
"ASN": "AS15169"
}]}` mapping="Address:Address" out_path="res"
Example Output
{
"res": [
{
"ANS": 15169,
"ASN": "AS15169",
"Address": "8.8.8.8",
"CountryName": "United States",
"Hostname": "dns.google",
"ISP": "Google LLC",
"Latitude": 37.4056,
"Longitude": -122.0775,
"Org": "Google LLC",
"Port": [
53
]
},
{
"ANS": 15169,
"Address": "8.8.4.4",
"Hostname": "dns.google"
}
]
}