interface RecursiveShape { [key: string]: boolean | string | RecursiveShape } const processOneLevel = ( prefix: string, getValue: (key: string, value: string) => any, obj: T ): T => { return Object.entries(obj).reduce((acc, [key, value]) => { const keyPath = prefix ? `${prefix}.${key}` : key if (value && typeof value === 'object') { // @ts-expect-error our shape traversal kinda sucks :( acc[key] = processOneLevel(keyPath, getValue, value) } else { // @ts-expect-error our shape traversal kinda sucks :( acc[key] = getValue(keyPath, value) } return acc }, {} as T) } /** * Take this nested object, keep the shape, but make the final keys the dot delimited string * path of the nested key */ export const transformNestedObject = ( getValue: (key: string, value: string) => any, obj: T ): T => processOneLevel('', getValue, obj) const flattenOneLevel = ( prefix: string, getValue: (key: string, value: string) => any, target: Record, source: T ): Record => { return Object.entries(source).reduce((acc, [key, value]) => { const keyPath = prefix ? `${prefix}.${key}` : key if (value && typeof value === 'object') { flattenOneLevel(keyPath, getValue, target, value as T) } else { if (typeof value !== 'function') { // @ts-expect-error our shape traversal kinda sucks :( acc[keyPath] = getValue(keyPath, value) } } return acc }, target as T) } export const flattenNestedObject = ( obj: T ): Record => flattenOneLevel('', (_k, v) => v, {}, obj)