Actualmente tengo un método existente en el que convierto todos los pares de valores clave en cadenas
export function convertObjValueToString<T>(data: Record<string, T>): Record<string, string> { return Object.keys(data).reduce((acc, key) => { return { ...acc, [key]: String(data[key]), }; }, {}); }pero actualmente está limitado al primer nivel de un objeto. Quiero que pruebe más profundamente si, por ejemplo, el valor es otro objeto secundario
{ id: 2, metadata: { booleanValue: false, someOtherKey: { booleanValue: false } } }el resultado esperado seria:
{ id: '2', metadata: { booleanValue: 'false', someOtherKey: { booleanValue: 'false' } } }Puede usar Object.entries() para convertir el objeto en una matriz de [key, value] , mapear la matriz de pares y transformar según el tipo, y luego volver a convertirlo en un objeto usando Object.fromEntries() :
const convertObjValueToString = data => { if (Array.isArray(data)) return data.map(convertObjValueToString) if (data !== null && typeof data === 'object') return Object.fromEntries( Object.entries(data) .map(([k, v]) => [k, convertObjValueToString(v)]) ) return String(data) } const obj = { id: 2, metadata: { booleanValue: false, someOtherKey: { booleanValue: false, nullKey: null, undefinedKey: undefined, } } } const result = convertObjValueToString(obj) console.log(result)Deberá usar un tipo recursivo para describir el objeto original y luego el objeto resultante ( TS Playground ):
type NestedValues<T> = | T | { [property: string]: NestedValues<T> } | NestedValues<T>[]; const convertObjValueToString = (data: NestedValues<any>): NestedValues<string> => { if (Array.isArray(data)) return data.map(convertObjValueToString) if (typeof data === 'object') return Object.fromEntries( Object.entries(data) .map(([k, v]) => [k, convertObjValueToString(v)]) ) return String(data) }export function convertObjValueToString<T extends object>(data: Record<string, T>): Record<string, string> { return Object.keys(data).reduce((acc, key) => { const currentPropValue = data[key]; if(currentPropValue === Object(currentPropValue)) { return { ...acc, [key]: convertObjValueToString(currentPropValue) }; } return { ...acc, [key]: String(data[key]), }; }, {}); } Obtuve la solución correcta, pero tengo problemas para arreglar los tipos, ya que obtengo un argument of type 'T' is not assignable to parameter of type 'Record<string, object>' bajo mi condición if
Considere este ejemplo:
type Values = number | boolean | string type Dictionary = { [prop: string]: Dictionary | Values } const isObject = (data: unknown): data is Dictionary => typeof data === 'object' && data !== null type ObjToString<Obj extends Dictionary> = { [Prop in keyof Obj]: (Obj[Prop] extends Dictionary ? ObjToString<Obj[Prop]> : (Obj[Prop] extends Values ? `${Obj[Prop]}` : never) ) } const record = < Key extends PropertyKey, Value >(key: Key, value: Value) => ({ [key]: value }) const merge = < Obj extends Dictionary, Part extends Dictionary >(obj: Obj, part: Part) => ({ ...obj, ...part }) const convert = < Data extends Dictionary >(data: Data): ObjToString<Data> => Object.keys(data).reduce((acc, elem) => { const value = data[elem]; const maker = isObject(value) ? convert : String return merge(acc, record(elem, maker(value))) }, {} as ObjToString<Data>) const result = convert({ id: 2, metadata: { booleanValue1: false, someOtherKey: { booleanValue2: false } } }) const id = result.id // `${number}` const booleanValue2 = result.metadata.someOtherKey.booleanValue2 // "false" Values : representa los valores primitivos permitidos del objeto
Dictionary : representa la forma/interfaz permitida del argumento
isObject - protector de tipo personalizado. Comprobar si el valor pasado es un objeto o no
ObjToString : representación de tipos de la lógica empresarial. Convierte objeto pasado a objeto donde todos los valores primitivos están en cadena
record - pequeño ayudante (puede estar en línea)
merge - ayudante pequeño (se puede incorporar)
convert - función principal. Repasa recursivamente cada tecla y convierte el valor en cadena o realiza una llamada recursiva
Como habrá notado, se infiere el result y TS sabe qué propiedades están permitidas y cuáles no. Tenga en cuenta que he pasado el valor literal del objeto. Si pasa un TS de referencia, aún infiere las propiedades permitidas, pero los valores serán más amplios. Por ejemplo, en lugar de "false" , obtendrá "true"|"false"
Si su argumento debe coincidir con alguna interfaz, es mejor porque en ese caso TS inferirá las propiedades requeridas con mayor precisión.