npm package: @ramster/general-tools
Description: General tools and helpers, part of the ramster open-source software toolkit. Can be used both in the browser and in node.
Author: Rumen Rumenov
Latest version: 2.3.0

![npm](https://img.shields.io/npm/v/@ramster/general-tools.svg)
![node](https://img.shields.io/node/v/@ramster/general-tools.svg)
![pipeline](https://gitlab.com/ramster-universe/general-tools/badges/master/pipeline.svg)
[![Coverage Status](https://coveralls.io/repos/gitlab/ramster-universe/general-tools/badge.svg?branch=master)](https://coveralls.io/gitlab/ramster-universe/general-tools?branch=master)
<!-- dependencies badge -->
<!-- devDependencies badge -->
<!-- vulnerabilities badge -->
![npm](https://img.shields.io/npm/dt/@ramster/general-tools.svg)
![npm](https://img.shields.io/npm/dm/@ramster/general-tools.svg)
<br/>

# Ramster General Tools
A general toolkit module that contains various utility methods. Can be used both in the browser and in node.<br/>
It's mostly meant as a companion to ramster and ramster-ui, but works just as fine on its own.

## Table Of Contents
- [Getting Started](#getting-started)
- [arraySort](#arraysort) - Sorts an array of objects by a list of inner properties. Supports SQL-style sorting by multiple field names and directions.
- [changeKeyCase](#changekeycase) - Changes the case of all keys in an object or string and its children between loweCamelCase and UpperCamelCase, based on the provided map.
- [checkRoutes](#checkroutes) - Checks whether a route exists in a list of HTTP routes. Supports ExpressJS-style route parameters, i.e. /users/item/:id.
- [decodeQueryValues](#decodequeryvalues) - Recursively performs decodeURIComponent on an object or value and returns the decoded object.
- [emptyToNull](#emptytonull) - Takes an object or value and transforms undefined, null and empty strings to null. Recursively does so for objects without mutating the provided data.
- [findVertexInGraph](#findvertexingraph) - Finds a vertex in a graph, then returns it and its path.
- [flattenObject](#flattenobject) - Takes a deeply nested object and flattens it into top-level key-value pairs. Particularly suitable for turning nested objects into get params.
- [getNested](#getnested) - Extracts a value from a deeply nested object, for example foo.bar.0.baz from {foo: {bar: [{baz: 'test'}]}}.
- [setNested](#setnested) - Sets a value in a deeply nested object, for example foo.bar.0.baz in {foo: {bar: [{baz: 'test'}]}}.
- [stringifyNestedObjects](#stringifynestedobjects) - Takes a nested object and turns its non-Date, non-null object keys into stringified _json_ keys, which can later be decoded by the decodeQueryValues method.

## Getting Started
It's really simple - just do
```
npm i @ramster/general-tools 
```
and you're good to go. Both the "import" and "require" syntax are supported, so either of these are valid:
```js
import {getNested} from '@ramster/general-tools'
const {getNested} = require('@ramster/general-tools')
```
<br/>



# arraySort
Sorts an array of objects by a list of inner properties. Supports SQL-style sorting by multiple field names and directions.

```js
import {arraySort, IArraySortSortingDirections} from '@ramster/general-tools'

const arrayToBeSorted = [
	{firstName: 'John', lastName: 'Doe'},
	{firstName: 'Jane', lastName: 'Doe'},
	{firstName: 'Peter', lastName: 'Parker'},
	{firstName: 'Peter', lastName: 'parker'}
]
const sortedArray = arraySort(
	arrayToBeSorted,
	[
		{direction: IArraySortSortingDirections.Ascending, fieldName: 'firstName'},
		{direction: IArraySortSortingDirections.Descending, fieldName: 'lastName'}
	],
	{caseSensitive: true}
)
// this will give us:
// [
//     {firstName: 'Jane', lastName: 'Doe'}
//     {firstName: 'John', lastName: 'Doe'}
//     {firstName: 'Peter', lastName: 'parker'}
//     {firstName: 'Peter', lastName: 'Parker'}
// ]
console.log(sortedArray)
```

#### Method parameters
| parameter | type                      | required | description                                                                        |
| --------- | ------------------------- | -------- | ---------------------------------------------------------------------------------- |
| `array`   | Array(any)                | yes      | The array to be sorted.                                                            |
| `orderBy` | Array(IArraySortOrderBy)  | yes      | The ordering options - the fields and directions to sort the array by (see below). |
| `options` | IArraySortOptions         | no       | Additonal ordering options, such as case sensitivity (see below).                  |

#### IArraySortOrderBy details
| parameter   | type                        | required | description                                                                             |
| ----------- | --------------------------- | -------- | --------------------------------------------------------------------------------------- |
| `direction` | IArraySortSortingDirections | yes      | Can be IArraySortSortingDirections.Ascending or IArraySortSortingDirections.Descending. |
| `fieldName` | string                      | yes      | The object field to sort by.                                                            |

#### IArraySortOptions details
| parameter       | type    | required | description                                                                                                                            |
| --------------- | ------- | -------- | -------------------------------------------------------------------------------------------------------------------------------------- |
| `caseSensitive` | boolean | no       | Whether the sorting should be case sensitive when sorting strings. If not set to true, strings will be lowercased before being sorted. |
<br/>



# changeKeyCase
Changes the case of all keys in an object or string and its children between loweCamelCase and UpperCamelCase, based on the provided map.

```js
import {changeKeyCase} from '@ramster/general-tools'

const keyCaseMap = {ProductId: 'productId', UserId: 'userId'},
	inputObject = {ProductId: 10, UserId: 15},
	inputString = '?ProductId=10&UserId=15'

const ouputObject = changeKeyCase(inputObject, keyCaseMap),
	outputString = changeKeyCase(inputString, keyCaseMap)

// this will give us:
// {productId: 10, userId: 15}
console.log(outputObject)

// this will give us:
// '?productId=10&userId=15'
console.log(outputString)
```

#### Method parameters
| parameter | type                                  | required | description                                                                                                                       |
| --------- | ------------------------------------- | -------- | -------------------------------------------------------------------------------------------------------------------------------- |
| `keyMap`  | Object - {[inputKey: string]: string} | yes      | The map of keys to be converted, i.e. {statusId: 'StatusId', productId: 'ProductId'} or {StatusId: 'statusId', ProductId: 'productId'}.                                                            |
| `input`   | string or Object                      | yes      | The input string or object.                                                                                                       |
<br/>



# checkRoutes
Changes the case of all keys in an object or string and its children between loweCamelCase and UpperCamelCase, based on the provided map.

```js
import {checkRoutes} from '@ramster/general-tools'

const routes = [
	'/main',
	'/api/:component/list'
]
const firstResult = checkRoutes('/api/users/list', routes),
	secondResult = checkRoutes('/foo/bar', routes)
// this will give us:
// true
console.log(firstResult)

// this will give us:
// false
console.log(secondResult)
```

#### Method parameters
| parameter  | type   | required | description                      |
| ---------- | ------ | -------- | -------------------------------- |
| `route`    | string | yes      | The route to be checked.         |
| `routes`   | string | yes      | The array of routes to check in. |
<br/>



# decodeQueryValues
Recursively performs decodeURIComponent on an object or value and returns the decoded object. Additionally, if an object is provided, any key that starts with "_json_" will be parsed using JSON.parse().

```js
import {decodeQueryValues} from '@ramster/general-tools'

const inputObject = {
	foo: '%2Fbar',
	number: '15',
	boolean: 'true',
	_json_test: '{"key1": "value1", "key2": "35", "key3": "false"}'
}
const firstResult = decodeQueryValues(inputObject),
	secondResult = decodeQueryValues(inputObject, {castTrueAndFalseToBoolean: true, parseNumbersToFloat: true)
// this will give us:
// {
//     foo: '/bar',
//     number: '15',
//     boolean: 'true',
//     test: {
//         key1: 'value1',
//         key2: '35',
//         key3: 'false'
//     }
// }
console.log(firstResult)

// this will give us:
// {
//     foo: '/bar',
//     number: 15,
//     boolean: true,
//     test: {
//         key1: 'value1',
//         key2: 35,
//         key3: false
//     }
// }
console.log(secondResult)
```

#### Method parameters
| parameter   | type                      | required | description                                                                                                                                      |
| ----------- | ------------------------- | -------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `input`     | any                       | yes      | The object or value to be decoded.                                                                                                               |
| `options`   | IDecodeQueryValuesOptions | no       | (see below) Flags for additonal processing to be performed on the input, such as casting strings to booleans and parsing string numbers to float |

#### IDecodeQueryValuesOptions details
| parameter                   | type    | required | description                                                     |
| --------------------------- | ------- | -------- | --------------------------------------------------------------- |
| `castTrueAndFalseToBoolean` | boolean | no       | Whether to attempt to cast "true" to true and "false" to false. |
| `parseNumbersToFloat`       | boolean | no       | Whether to attempt to parse string numbers to float.            |
<br/>



# emptyToNull
Takes an object or value and transforms undefined, null and empty strings to null. Recursively does so for objects without mutating the provided data.

```js
import {emptyToNull} from '@ramster/general-tools'

// this will give us:
// {test: null, test3: null, test4: 'test'}
console.log(emptyToNull({test: null, test2: undefined, test3: '', test4: 'test'}))
```

#### Method parameters
| parameter | type  | required | description                       |
| --------- | ----- | -------- | --------------------------------- |
| `data`    | any   | yes      | The object or value to transform. |
<br/>



# findVertexInGraph
Finds a vertex in a graph, then returns it and its path in the form of an IFindVertexInGraphReturnData object (see the last table below for more info on this object).

```js
import {findVertexInGraph, IFindVertexInGraphSearchTypes} from '@ramster/general-tools'

// this will give us:
// {pathToVertex: '0.children.10.children.15', vertex: {data: 't1'}}
console.log(
	findVertexInGraph(
		{
			0: {
				children: {
					10: {
						children: {
							15: {data: 't1'}
						},
						data: 'test'
					}
				},
				data: 't0'
			}
		}
	),
	15,
	{searchType: IFindVertexInGraphSearchTypes.DFS}
)
```

#### Method parameters
| parameter  | type                          | required | description                                                               |
| ---------- | ----------------------------- | -------- | ------------------------------------------------------------------------- |
| `graph`    | IFindVertexInGraphGraph       | yes      | The graph to search in.                                                   |
| `vertexId` | string                        | yes      | The id of the vertex to search for.                                       |
| `options`  | IFindVertexInGraphOptions     | yes      | Method options, such as the search type (currently DFS only) - see below. |

#### IFindVertexInGraphGraph details
| parameter            | type                          | required | description         |
| -------------------- | ----------------------------- | -------- | ------------------- |
| `[vertexId: string]` | IFindVertexInGraphGraphVertex | yes      | The child vertices. |

#### IFindVertexInGraphGraphVertex details
| parameter  | type                    | required | description                                       |
| ---------- | ----------------------- | -------- | ------------------------------------------------- |
| `children` | IFindVertexInGraphGraph | no       | The child vertices in the form of a graph.        |
| `data`     | any                     | no       | A data object containing any data for the vertex. |

#### IFindVertexInGraphOptions details
| parameter           | type                          | required | description                                                                                 |
| ------------------- | ----------------------------- | -------- | ------------------------------------------------------------------------------------------- |
| `currentVertexPath` | string                        | no       | The base path at the start of method executing. Automatically set by the method internally. |
| `searchType`        | IFindVertexInGraphSearchTypes | yes      | The type of search algorithm to use (current only DFS is supported).                        |

#### IFindVertexInGraphSearchTypes enum details
| value | description                                          |
| ------| ---------------------------------------------------- |
| `DFS` | Makes the method run a depth-first search algorithm. |

#### IFindVertexInGraphReturnData details
| parameter      | type                                  | description                                         |
| -------------- | ------------------------------------- | --------------------------------------------------- |
| `pathToVertex` | string or null                        | The path to the vertex, including the vertex id.    |
| `vertex`       | IFindVertexInGraphGraphVertex or null | The vertex itself, including its children and data. |
<br/>



# flattenObject
Takes a deeply nested object and flattens it into top-level key-value pairs. Particularly suitable for turning nested objects into get params.

```js
import {flattenObject} from '@ramster/general-tools'

let result = flattenObject(
	{
		testKey: [{testKey1: 'test'}, date],
		testKey2: 'test test test',
		testKey3: {testKey4: [null, 'test', {tq: 11}]},
		testKey4: null
	}
)
// this will give us:
// [
// 	{key: 'testKey[][testKey1]', value: 'test'},
// 	{key: 'testKey[]', value: date.toString()},
// 	{key: 'testKey2', value: 'test test test'},
// 	{key: 'testKey3[testKey4][]', value: 'test'},
// 	{key: 'testKey3[testKey4][][tq]', value: 11}
// ]
console.log(result)
```

#### Method parameters
| parameter | type                       | required | description            |
| --------- | -------------------------- | -------- | ---------------------- |
| `object`  | {[fieldName: string]: any} | yes      | The object to flatten. |
<br/>



# getNested
Extracts a value from a deeply nested object, for example foo.bar.0.baz from {foo: {bar: [{baz: 'test'}]}}.

```js
import {getNested} from '@ramster/general-tools'

const inputObject = {
	foo: [
		{bar: 'test'},
		{bar: 'test2'},
		{bar: 'test'},
		{bar: 'test3'}
	]
}
const firstResult = getNested(inputObject, 'foo.0.bar'),
	secondResult = getNested(inputObject, 'foo.bar'),
	thirdResult = getNested(inputObject, 'foo.bar', {arrayItemsShouldBeUnique: true})
// this will give us:
// 'test'
console.log(firstResult)

// this will give us:
// ['test', 'test2', 'test', 'test3']
console.log(secondResult)

// this will give us:
// ['test', 'test2', 'test3']
console.log(thirdResult)
```

#### Method parameters
| parameter   | type                      | required | description                                                 |
| ----------- | ------------------------- | -------- | ----------------------------------------------------------- |
| `parent`    | any                       | yes      | The object to retrieve the value from.                      |
| `field`     | string                    | yes      | The path to the field.                                      |
| `options`   | IGetNestedOptions         | no       | Method executiom options, such as arrayItemsShouldBeUnique. |

#### IGetNestedOptions details
| parameter                  | type    | required | description                                                                                                           |
| -------------------------- | ------- | -------- | --------------------------------------------------------------------------------------------------------------------- |
| `arrayItemsShouldBeUnique` | boolean | no       | If a field in the path is an array, whether its values should be taken as-is or unique. (see the third example above) |
<br/>



# setNested
Sets a value in a deeply nested object, for example foo.bar.0.baz in {foo: {bar: [{baz: 'test'}]}}.

```js
import {setNested} from '@ramster/general-tools'

const inputObject = {
	foo: [
		{bar: 'test'},
		{bar: 'test2'},
		{bar: 'test'},
		{bar: 'test3'}
	]
}
const firstResult = setNested(inputObject, 'foo.0.bar', 'TEST'),
	secondResult = setNested(inputObject, 'foo.bar', 'TEST'),
	thirdResult = setNested(inputObject, 'foo.baz.bar', 'TEST')
// this will give us:
// true,
// {
//     foo: [
//         {bar: 'TEST'},
//         {bar: 'test2'},
//         {bar: 'test'},
//         {bar: 'test3'}
//     ]
// }
console.log(firstResult, inputObject)

// this will give us:
// true,
// {
//     foo: [
//         {bar: 'TEST'},
//         {bar: 'TEST'},
//         {bar: 'TEST'},
//         {bar: 'TEST'}
//     ]
// }
console.log(secondResult, inputObject)

// this will give us:
// false
console.log(thirdResult)
```

#### Method parameters
| parameter   | type   | required | description                            |
| ----------- | ------ | -------- | -------------------------------------- |
| `parent`    | any    | yes      | The object to retrieve the value from. |
| `field`     | string | yes      | The path to the field.                 |
| `value`     | any    | yes      | The value to set for the field.        |
<br/>



# stringifyNestedObject
Takes a nested object and turns its non-Date, non-null object keys into stringified _json_ keys, which can later be decoded by the decodeQueryValues method.

```js
import {stringifyNestedObject} from '@ramster/general-tools'

let date = new Date()
let result = stringifyNestedObject(
	{
		testKey: [{testKey1: 'test'}, date],
		testKey2: 'test test test',
		testKey3: {testKey4: [null, 'test', {tq: 11}]}
	}
)
// this will give us:
// {
// 	_json_testKey: '[{"testKey1":"test"},"2020-05-20T09:20:55.327Z"]',
// 	testKey2: 'test test test',
// 	_json_testKey3: '{"testKey4":[null,"test",{"tq":11}]}'
// }
console.log(result)
```

#### Method parameters
| parameter | type                 | required | description              |
| --------- | -------------------- | -------- | ------------------------ |
| `data`    | {[key: string]: any} | yes      | The object to stringify. |