JSON array sort, OpenWhisk FaaS
This flow implements an algorithm for sorting arrays of JSON objects according to primary and/or secondary properties. It can sort JSON objects by numeric value or string value. It implements the OpenWhisk API specification and as such it is used as web service.
Example Input:
{
"props": ["value1", "value2.value3"],
"order": ["dsc", "asc"],
"data": [{
"value1": "a",
"value5": "burger",
"value2": {
"value3": 2
}
},
{
"value1": "a",
"value2": {
"value3": 1
}
},
{
"value1": "b",
"value2": {
"value3": 3
}
},
{
"value1": "b",
"value2": {
"value3": 1
}
},
{
"value1": "a",
"value2": {
"value3": 3
}
},
{
"value1": "b",
"value2": {
"value3": 2
}
},
{
"value1": "c",
"value2": {
"value3": 1
}
},
{
"value1": "c",
"value2": {
"value3": 2
}
},
{
"value1": "c",
"value2": {
"value3": 3
}
}]
}
The props property must be an array of size 1 or 2, containing values of type string. The values represent the property names on which the sorting will take place. If an array of size 2 is provided the sorting will be done primarily on props[0] property and secondary on props[1]. The props values can represent nested properties from the JSON files, if so the values must be formated using the dot notation. The values from the JSON object that the properties represent, and as such the ordering can be done on, must be of type number or string.
By including an order property array (optional) you can specify whether the order will be ascending or descending. The values must be one of 'asc' for ascending or 'dsc' for descending. The ordering can be done idependently on each property. If no order array is provided the sorting will be ascending.
The data property contains an array with the JSON objects that will be sorted. The sorting can take place on numeric values or string values. The properties specified on the props array must contain the same type of value in the JSON objects for each property, but it can be different for different property. For example props[0] property can contain numbers but props[1] property can be strings, as long as the values between the JSON objects in the data array are consistent. The JSON objects can have different fields to eachother, as long as, the properties specified in the props array are present and of the same type between all JSON objects.
In the above example the JSON objects in the data array will be sorted by primary value1 property descending and by secondary value2.value3 property ascending.
The flow is containerized with docker and hosted on docker hub here in order to be used with OpenWhisk platform.
To learn how to setup and user OpenWhisk click here.
When you invoke the action it is recommended to contain your JSON data in a file and pass it as a parameter to the action with the wsk cli parameter "--param-file [YOUR_JSON_FILE]".
[{"id":"fdae3494febd28da","type":"tab","label":"JSON Sort OpenWhisk","disabled":false,"info":"","env":[]},{"id":"4826e1b8.c7fb2","type":"http in","z":"fdae3494febd28da","name":"","url":"/init","method":"post","upload":false,"swaggerDoc":"","x":440,"y":280,"wires":[["12b199d2.880ba6"]]},{"id":"b25a5bda.3c55d8","type":"http in","z":"fdae3494febd28da","name":"","url":"/run","method":"post","upload":false,"swaggerDoc":"","x":80,"y":40,"wires":[["932f1a749068d194"]]},{"id":"12b199d2.880ba6","type":"http response","z":"fdae3494febd28da","name":"","statusCode":"","headers":{},"x":730,"y":280,"wires":[]},{"id":"b16901b9.0f459","type":"http response","z":"fdae3494febd28da","name":"","statusCode":"","headers":{},"x":730,"y":200,"wires":[]},{"id":"34cfecf52257e7e2","type":"catch","z":"fdae3494febd28da","name":"","scope":null,"uncaught":false,"x":80,"y":200,"wires":[["417fa2d038e9b413"]]},{"id":"417fa2d038e9b413","type":"function","z":"fdae3494febd28da","name":"ADD ERROR INFO","func":"var payload=msg.payload;\nmsg.payload={};\n\nmsg.payload.error=msg.error;\nmsg.payload.error.payload=payload;\nreturn msg;","outputs":1,"noerr":0,"initialize":"","finalize":"","libs":[],"x":410,"y":200,"wires":[["b16901b9.0f459"]]},{"id":"932f1a749068d194","type":"function","z":"fdae3494febd28da","name":"prechecks","func":"function getNestedValue(object, path) {\n if (!path) return undefined;\n\n let prop, props = path.split('.');\n\n for (var i = 0, iLen = props.length - 1; i < iLen; i++) {\n prop = props[i];\n let candidate = object[prop];\n if (candidate !== undefined) {\n object = candidate;\n } else {\n break;\n }\n }\n return object[props[i]];\n}\n\n\nif (!msg.payload.value.props) {\n throw new Error('\"props\" property does not exist');\n}\nif (!Array.isArray(msg.payload.value.props)) {\n throw new Error('\"props\" property is not an array');\n}\n\nif (msg.payload.value.props.length > 2 || msg.payload.value.props.length < 1) {\n throw new Error('\"props\" array must be of size 1 or 2');\n}\n\nfor (let item of msg.payload.value.props) {\n if (typeof(item) !== 'string') {\n throw new Error('\"props\" value at index ' + msg.payload.value.props.indexOf(item) + ', is not of type string');\n }\n}\n\nif (!msg.payload.value.data) {\n throw new Error('missing \"data\" property');\n}\n\n\nfor (let prop of msg.payload.value.props) {\n for (let item of msg.payload.value.data){\n if (getNestedValue(item, prop) === undefined){ \n throw new Error('provided \"props\" property missing from object');\n }\n }\n}\n\nif (msg.payload.value.order) {\n for (let item of msg.payload.value.order) {\n if (!(item === 'asc' || item === 'dsc')) {\n throw new Error('invalid \"order\" parameters');\n }\n }\n}\n\nreturn msg;","outputs":1,"noerr":0,"initialize":"","finalize":"","libs":[],"x":290,"y":40,"wires":[["086e27575ca5b8c8"]]},{"id":"64b945946c908358","type":"switch","z":"fdae3494febd28da","name":"","property":"payload.props.length","propertyType":"msg","rules":[{"t":"gt","v":"1","vt":"str"},{"t":"else"}],"checkall":"true","repair":false,"outputs":2,"x":490,"y":80,"wires":[["6a83b89ad99cd3cf"],["a3ebff8b5dd29534"]]},{"id":"086e27575ca5b8c8","type":"function","z":"fdae3494febd28da","name":"primary sort","func":"function getNestedValue(object, path) {\n if (!path) return undefined;\n\n let prop, props = path.split('.');\n \n for (var i = 0, iLen = props.length - 1; i < iLen; i++) {\n prop = props[i];\n let candidate = object[prop];\n if (candidate !== undefined) {\n object = candidate;\n } else {\n break;\n }\n }\n return object[props[i]];\n}\n\nconst props = msg.payload.value.props\nconst order = msg.payload.value.order\nlet data = msg.payload.value.data\n\nif (!order || order && order[0] === 'asc') {\n data.sort((a, b) => {\n return ('' + getNestedValue(a, props[0])).localeCompare(getNestedValue(b, props[0]))\n })\n} else if (order[0] === 'dsc') {\n data.sort((a, b) => {\n return ('' + getNestedValue(b, props[0])).localeCompare(getNestedValue(a, props[0]))\n })\n}\n\nmsg.payload = {\n 'props': props,\n 'order': order,\n 'data': data\n}\n\nreturn msg;","outputs":1,"noerr":0,"initialize":"","finalize":"","libs":[],"x":290,"y":120,"wires":[["64b945946c908358"]]},{"id":"6a83b89ad99cd3cf","type":"function","z":"fdae3494febd28da","name":"secondary sort","func":"function getNestedValue(object, path) {\n if (!path) return undefined;\n\n let prop, props = path.split('.');\n\n for (var i = 0, iLen = props.length - 1; i < iLen; i++) {\n prop = props[i];\n\n let candidate = object[prop];\n if (candidate !== undefined) {\n object = candidate;\n } else {\n break;\n }\n }\n return object[props[i]];\n}\n\nconst props = msg.payload.props\nconst order = msg.payload.order\nconst data = msg.payload.data\n\n//group according to primary property\nlet groups = []\nlet group = []\nlet primary_prop = props[0]\nfor (let item of data) {\n if (item[props[0]] === primary_prop) { \n group.push(item)\n } else {\n groups.push(group)\n group = []\n group.push(item)\n primary_prop = item[props[0]]\n }\n}\ngroups.push(group)\n\n//sort each group\nfor (let group of groups) {\n //ascending\n if (!order || order && !order[1] || order && order[1] && order[1] === 'asc') {\n group.sort((a, b) => {\n return ('' + getNestedValue(a, props[1])).localeCompare(getNestedValue(b, props[1]))\n })\n //descending\n } else if (order[1] === 'dsc') {\n group.sort((a, b) => {\n return ('' + getNestedValue(b, props[1])).localeCompare(getNestedValue(a, props[1]))\n })\n }\n}\n\n//recombine data\nlet sorted_data = []\nfor (let group of groups) {\n for (let item of group) {\n sorted_data.push(item)\n }\n}\n\nmsg.payload = {\n 'data': sorted_data\n}\nreturn msg;","outputs":1,"noerr":0,"initialize":"","finalize":"","libs":[],"x":700,"y":40,"wires":[["a3ebff8b5dd29534"]]},{"id":"a3ebff8b5dd29534","type":"function","z":"fdae3494febd28da","name":"format payload","func":"const data = msg.payload.data\nmsg.payload = { data } //openwhisk\nreturn msg;","outputs":1,"noerr":0,"initialize":"","finalize":"","libs":[],"x":700,"y":120,"wires":[["b16901b9.0f459"]]}]