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"
    }
  ]
}