/** * Remap an object to an array through a function * * (Same as Array.map) * @param obj the object to remap * @param fn the function to run for each key: value pairs */ export function objectMap( obj: Record, fn: (value: T, key: string, index: number) => J ): Array { const list: Array = [] objectLoop(obj, (item, key, index) => { list.push(fn(item, key, index)) }) return list } /** * Loop through the object * @param obj the object to loop through * @param fn the function to run for each childs */ export function objectLoop( obj: Record, fn: (value: T, key: string, index: number) => boolean | void ): boolean { const keys = objectKeys(obj) for (let index = 0; index < keys.length; index++) { const key = keys[index] const stop = fn(obj[key], key, index) if (stop === false) { return false } } return true } /** * Transform an object to an array removing the keys * @param obj the object to transform */ export function objectToArray(obj: Record): Array { return Object.values(obj) } /** * return the keys of th object * @param obj the object */ export function objectKeys(obj: Record): Array { return Object.keys(obj) } /** * return the length of an object * @param obj the object */ export function objectSize(obj: Record): number { return objectKeys(obj).length } /** * Sort an object by its keys * * Same as Array.sort * @param obj the object to sort * @param fn (Optionnal) the function to run to sort */ export function objectSort = Record>( obj: T, fn?: Array | ((a: keyof T, b: keyof T) => number) ): T { const ordered: any = {} let sortedKeys: Array = [] if (Array.isArray(fn)) { sortedKeys = fn.concat(objectKeys(obj).filter((k) => !fn.includes(k))) } else { sortedKeys = objectKeys(obj).sort(fn) } for (const key of sortedKeys) { ordered[key] = obj[key] } return ordered } /** * Deeply clone an object * @param obj the object to clone * @deprecated Replace with objectClone */ export function cloneObject>(obj: T): T { return objectClone(obj) } /** * Deeply clone an object * @param obj the object to clone */ export function objectClone>(obj: T): T { if (typeof obj !== 'object') { const v = obj return v } if (Array.isArray(obj)) { const arr: Array = [] for (const item of obj) { arr.push(objectClone(item)) } return arr as unknown as T } const clone: Partial = {} objectLoop(obj, (value, key) => { if (typeof value === 'object' && value != null) { clone[key as Extract] = objectClone(value) return } clone[key as Extract] = value }) return clone as T } /** * deeply set the value at the path given * * (Create sub object/array if not made) * * _NOTE: it is way quicker to use obj[path][path]... = value_ * @param obj the object to set the value * @param path the path * @param value the value */ export function objectSet(obj: Record, path: Array, value: any): void { let pointer = obj for (let index = 0; index < path.length; index++) { const key = path[index] if ((!Object.prototype.hasOwnProperty.call(pointer, key)) && (index+1) < path.length) { const key1 = path[index + 1] if (typeof key1 === 'number') { pointer[key] = [] } else { pointer[key] = {} } } // if last index if ((index+1) === path.length) { pointer[key] = value if (value === undefined) { delete pointer[key] } break } // move pointer to new key pointer = pointer[key] } } /** * deeply compare objects and return if they are equal or not * @param x the first object * @param y the second object */ export function objectEqual(x: Record, y: Record): boolean { if (objectSize(x) !== objectSize(y)) { return false } const res = objectLoop(x, (item, key) => { if (!(key in y)) { return false } const item2 = y[key] if (item === null && item2 === null) { return true } if (typeof item === 'object' && typeof item2 === 'object') { return objectEqual(item, item2) } return item === item2 }) return res } export default { objectMap, objectLoop, objectToArray, objectKeys, objectSize, objectSort, cloneObject, objectClone, objectSet, objectEqual }