import { isDate } from 'date-fns' import { unique } from '@/utilities/arrays' export function flip(obj: Record): Record { const result = {} as Record for (const key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) { result[obj[key]] = key } } return result } export function omit, K extends(keyof T)[]>(source: T, keys: K): Omit { const copy = { ...source } keys.forEach(key => delete copy[key]) return copy } export function clone(source: T): T { if (source === null || typeof source !== 'object') { return source } if (isDate(source)) { // eslint-disable-next-line @typescript-eslint/ban-ts-comment // @ts-expect-error return new Date(source) } // eslint-disable-next-line @typescript-eslint/ban-ts-comment // @ts-expect-error const copy = new source() for (const key in source) { copy[key] = clone(source[key]) } return copy } export function hasProperty>(needle: T, property: unknown): property is keyof T { return (typeof property === 'string' || typeof property === 'symbol') && property in needle } export type MapKeysCallback = (key: PreviousKey, value: Value) => NewKey export function mapKeys(object: Record, callback: MapKeysCallback): Record { const entries = Object.entries(object) as [K, V][] const result = {} as Record return entries.reduce((result, [key, value]) => { const newKey = callback(key, value) result[newKey] = object[key] return result }, result) } export type MapValuesCallback = (key: Key, value: PreviousValue) => NewValue export function mapValues(object: Record, callback: MapValuesCallback): Record { const entries = Object.entries(object) as [Key, PreviousValue][] const result = {} as Record return entries.reduce((result, [key, value]) => { result[key] = callback(key, value) return result }, result) } export type MapEntriesCallback = (key: PreviousKey, value: PreviousValue) => [NewKey, NewValue] export function mapEntries(object: Partial>, callback: MapEntriesCallback): Partial> { const entries = Object.entries(object) as [PreviousKey, PreviousValue][] const result = {} as Record return entries.reduce((result, [key, value]) => { const [newKey, newValue] = callback(key, value) result[newKey] = newValue return result }, result) } export function isEmptyObject(value: unknown): value is Record { return typeof value === 'object' && !Array.isArray(value) && value !== null && Object.keys(value).length === 0 } export function isTypeRequired>(value: Partial): value is Required { return Object.values(value).every(value => value !== undefined) } export function hasString>(obj: T, str: string): boolean { const values = Object.values(obj).map(val => val?.toString().toLowerCase() ?? '').join('') return values.includes(str.toLowerCase()) } export function isRecord(item: unknown): item is Record { return item !== null && typeof item === 'object' && !Array.isArray(item) && !isDate(item) } /** * @deprecated Please use lodash.merge instead. */ export function merge>(target: T, ...sources: T[]): T { if (sources.length === 0) { return target } const [source, ...rest] = sources const keys = unique([...Object.keys(target), ...Object.keys(source)]) for (const key of keys) { const targetValue: unknown = target[key] const sourceValue: unknown = source[key] if (targetValue === sourceValue) { continue } if (isRecord(targetValue) && isRecord(sourceValue)) { merge(targetValue, sourceValue) continue } if (isRecord(targetValue) && isRecord(source) && !(key in source)) { merge(targetValue, { [key]: {} }) continue } // this is really sloppy typescript... // eslint-disable-next-line @typescript-eslint/no-explicit-any target[key as keyof T] = source[key] as any } return merge(target, ...rest) } type EmptyObjectsRemoved> = { [P in keyof T]: T[P] extends Record ? EmptyObjectsRemoved | undefined : T[P]; } export function removeEmptyObjects>(input: T): EmptyObjectsRemoved { const response: Record = {} const keys = Object.keys(input) for (const key of keys) { const value = input[key] if (value === undefined) { continue } if (isRecord(value)) { const possiblyEmptyObject = removeEmptyObjects(value) if (Object.keys(possiblyEmptyObject).length) { response[key] = possiblyEmptyObject } continue } response[key] = value } return response as EmptyObjectsRemoved }