SortBy

This transformer will sort an array of dictionary values by keys in ascending or descending order.

python · Filters And Transformers

Source

import json
from collections import OrderedDict
from typing import Any

import demistomock as demisto  # noqa: F401
from CommonServerPython import *  # noqa: F401


def demisto_get(obj: Any, path: Any) -> Any:
    r"""
    This is an extended function of demisto.get().
    The `path` argument parameter supports a syntax of path escaped with backslash
    in order to support a key icluding period charactors.

    e.g.
       xxx
        + x.y.z
         + zzz

       -> path: xxx.x\.y\.z.zzz

    :param obj: The root node.
    :param path: The path to get values in the node.
    :return: The value(s) specified with `path` in the node.
    """

    def split_context_path(path: str) -> list[str]:
        """
        Get keys in order from the path which supports a syntax of path escaped with backslash.

        :param path: The path.
        :return: The keys whose escape charactors are removed.
        """
        nodes = []
        node = []
        itr = iter(path)
        for c in itr:
            if c == "\\":
                try:
                    node.append(next(itr))
                except StopIteration:
                    node.append("\\")
            elif c == ".":
                nodes.append("".join(node))
                node = []
            else:
                node.append(c)
        nodes.append("".join(node))
        return nodes

    if not isinstance(obj, dict):
        return None

    for part in split_context_path(path):
        if obj and part in obj:
            obj = obj[part]
        else:
            return None
    return obj


class Key:
    """
    This is a class to provide you a key from an any types of values to sort it.
    That allows you to sort values even if them have different types of values.
    The key given by Key.get() can be used for list.sort().
    """

    def __init__(self, value: Any, path: Optional[str]) -> None:
        """
        Initialize the key.

        :param value: The value to set key, or the node from which to get the value if the `path` is given.
        :param path: The path to get values in the node.
        """
        self.__value = value if path is None else demisto_get(value, path)

    def __get_type_order(self) -> int:
        if self.__value is None:
            return 0
        elif isinstance(self.__value, bool):
            return 1
        elif isinstance(self.__value, int | float):
            return 2
        elif isinstance(self.__value, str):
            return 3
        else:
            return 4

    def __get_key(self) -> Any:
        def __get(value: Any) -> Any:
            if value is None:
                return 0
            elif isinstance(value, bool | int | float | str):
                return value
            elif isinstance(value, dict):
                return OrderedDict((k, __get(value[k])) for k in sorted(value.keys()))
            elif isinstance(value, list):
                return [__get(v) for v in value]
            else:
                return value

        v = __get(self.__value)
        if v is None or isinstance(v, bool | int | float | str):
            return v
        else:
            return json.dumps(v)

    def get(self) -> tuple[int, Any]:
        """
        Get the key from the value.

        :return: The key which can be used for list.sort().
        """
        return self.__get_type_order(), self.__get_key()


def main():
    try:
        args = assign_params(**demisto.args())
        if value := args.get("value", []):
            descending_keys = argToList(args.get("descending_keys"))
            if paths := argToList(args.get("keys")):
                for path in reversed(paths):
                    value.sort(key=lambda x: Key(x, path).get(), reverse=path in descending_keys)
            else:
                descending = len(descending_keys) == 1 and descending_keys[0] == "*"
                value.sort(key=lambda x: Key(x, None).get(), reverse=descending)

        return_results(value)
    except Exception as err:
        # Don't return an error by return_error() as this is transformer.
        raise DemistoException(str(err))


if __name__ in ("__builtin__", "builtins", "__main__"):
    main()

README

This transformer will sort an array of dictionary values by keys in ascending or descending order.
When values have different types of data, the hierarchy is: null < bool < int/float < str < other (null is top in ascending order).

Script Data


Name Description
Script Type python3
Tags transformer, general
Cortex XSOAR Version 6.8.0

Inputs


Argument Name Description
value The array to sort.
keys Comma-separated list of ordering-keys specifying a sorting hierarchy
descending_keys Comma-separated list of keys to sort in descending order. ‘*’ is the special symbol to sort the array given without `keys` in descending order.

Outputs


There are no outputs for this script.


Examples-1

Here is a table for the sorting samples in the Examples-1.

Name Country Score
Alex US 30
Kate US 50
Chris US 20
Janet Australia 50
Steve Australia 40
Dora Australia  

The JSON data is below to be given to the value argument parameter of the transformer for the samples.

[
  {
    "Name": "Alex",
    "Country": "US",
    "Score": 30
  },
  {
    "Name": "Kate",
    "Country": "US",
    "Score": 50
  },
  {
    "Name": "Chris",
    "Country": "US",
    "Score": 20
  },
  {
    "Name": "Janet",
    "Country": "Australia",
    "Score": 50
  },
  {
    "Name": "Dora",
    "Country": "Australia"
  }
]

Sort by Country in ascending order.

keys: Country

descending_keys:

Output

It will give you the result like this. But the order except for the Country is not guaranteed.

Name Country Score
Janet Australia 50
Dora Australia  
Alex US 30
Kate US 50
Chris US 20
[
  {
    "Name": "Janet",
    "Country": "Australia",
    "Score": 50
  },
  {
    "Name": "Dora",
    "Country": "Australia"
  },
  {
    "Name": "Alex",
    "Country": "US",
    "Score": 30
  },
  {
    "Name": "Kate",
    "Country": "US",
    "Score": 50
  },
  {
    "Name": "Chris",
    "Country": "US",
    "Score": 20
  }
]

Sort by Country in ascending order, and sort by Score in ascending order with keeping the order by Country.

keys: Country, Score

descending_keys:

Output

It will give you the result like this. But the order except for the Country and Score is not guaranteed.

Name Country Score
Dora Australia  
Janet Australia 50
Chris US 20
Alex US 30
Kate US 50
[
  {
    "Name": "Dora",
    "Country": "Australia"
  },
  {
    "Name": "Janet",
    "Country": "Australia",
    "Score": 50
  },
  {
    "Name": "Chris",
    "Country": "US",
    "Score": 20
  },
  {
    "Name": "Alex",
    "Country": "US",
    "Score": 30
  },
  {
    "Name": "Kate",
    "Country": "US",
    "Score": 50
  }
]

Sort by Country in ascending order, and sort by Score in descending order with keeping the order by Country.

keys: Country, Score

descending_keys: Score

Output

It will give you the result like this. But the order except for the Country and Score is not guaranteed.

Name Country Score
Janet Australia 50
Dora Australia  
Kate US 50
Alex US 30
Chris US 20
[
  {
    "Name": "Janet",
    "Country": "Australia",
    "Score": 50
  },
  {
    "Name": "Dora",
    "Country": "Australia"
  },
  {
    "Name": "Kate",
    "Country": "US",
    "Score": 50
  },
  {
    "Name": "Alex",
    "Country": "US",
    "Score": 30
  }
  {
    "Name": "Chris",
    "Country": "US",
    "Score": 20
  }
]

Examples-2

Here is an array for the sorting samples in the Examples-2.
It will be given to the value argument parameter of the transformer for the samples.

[
  {
    "key": "value1"
  },
  2,
  0.5,
  0,
  1,
  null,
  "aaa",
  "ZZZ"
]

Sort in ascending order.

keys:

descending_keys:

Output

[
  null,
  0,
  0.5,
  1,
  2,
  "ZZZ",
  "aaa",
  {
    "key": "value1"
  }
]

Sort in descending order.

keys:

descending_keys: *

Output

[
  {
    "key": "value1"
  },
  "aaa",
  "ZZZ",
  2,
  1,
  0.5,
  0,
  null
]