• Jobs
  • About Us
  • professionals
    • Home
    • Jobs
    • Courses and challenges
    • Questions
    • Teachers
  • business
    • Home
    • Post vacancy
    • Our process
    • Pricing
    • Assessments
    • Payroll
    • Blog
    • Sales
    • Salary Calculator

0

198
Views
¿Cómo escribir correctamente una función genérica similar a Object.assign?

Estoy tratando de crear una función de "asignación predeterminada" en TypeScript, donde recorre las claves de la source , y si ese valor por la misma clave es nulo en el target , usará el valor de la source en su lugar. Aquí está mi intento:

 const assignDefault = <T, U>(target: T, source: U): T & U => { Object.keys(source).forEach(key => { // typecasted as Object.keys returns string[] const prop = target[key as keyof T] if (typeof prop === 'undefined' || prop === null) { // Error: Type 'U[keyof U]' is not assignable to type 'T[keyof T]'. target[key as keyof T] = source[key as keyof U] } }) return target // Error: Type 'T' is not assignable to type 'T & U'. }

Tomé prestados los genéricos de cómo se escribe Object.assign en TypeScript:

 ObjectConstructor.assign<T, U>(target: T, source: U): T & U;

Pero no pude encontrar una manera de sortear estos errores.

Patio de recreo

about 3 years ago · Santiago Trujillo
2 answers
Answer question

0

Esto es lo que se me ocurrió.

Un par de cosas a tener en cuenta:

typeof nunca devolverá "null" consulte: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/typeof

En segundo lugar, el tipo T & U espera una combinación tanto del destino como de la fuente. Puede lograrlo con bastante facilidad utilizando el operador de propagación para fusionarlos como se muestra en mi ejemplo.

[EDITAR] En lugar de estar restringido por el tipo que requiere. Completé la función modificada para que se ajuste más a su intención original.

 const assignDefault = (target: Record<string, unknown>, source: Record<string, unknown>): Record<string, unknown> => { for (const key in source) { if (target[key] === null || typeof target[key] === 'undefined') { target[key] = source[key]; } } return target; } const myTarget = { prop1: null, prop2: "some value", prop3: ["some", "other", "value"] } const mySource = { prop1: "hello world", prop2: "don't show this value", prop3: [], prop4: "a value not provided by target originally" } // expect /** * { * prop1: "hello world", * prop2: "some value", * prop3: ["some", "other", "value"], * prop4: "a value not provided by target originally" * } */ const newObj = assignDefault(myTarget, mySource); console.log(newObj);

Tenga en cuenta que esta función solo funciona realmente con objetos con pares de valores clave.

about 3 years ago · Santiago Trujillo Report

0

Tomé prestados los genéricos de cómo se escribe Object.assign

Hay una gran diferencia entre las definiciones de tipo y la implementación. El compilador está contento con : T & U de la biblioteca estándar porque no hay información contradictoria. Sin embargo, en su implementación, el tipo de target es solo T , mientras que la firma espera T & U , de ahí el error del compilador.

El compilador no es un tiempo de ejecución, por lo que aunque realiza un análisis de flujo de control, no puede inferir que forEach muta target . Sin embargo, hay formas de hacer que el compilador sea consciente de ello. El más simple es afirmar el tipo del valor ed return como any (para deshabilitar el verificador de tipo) o T & U :

 const assignDefault = <T, U>(target: T, source: U): T & U => { Object.keys(source).forEach((key) => { const prop = target[key as keyof T] if (typeof prop === 'undefined' || prop === null) { // Error: Type 'U[keyof U]' is not assignable to type 'T[keyof T]'. target[key as keyof T] = source[key as keyof U] } }); return target as T & U; };

Patio de recreo

Sin embargo, hay otro problema con el tipo de retorno de assignDefault : T & U es una intersección, que no es lo que desea. Para escribir correctamente "si es nulo, asignar", se necesita un tipo asignado con algunos tipos condicionales y extends las comprobaciones para determinar si se puede asignar una propiedad.

Tal tipo podría verse así:

 type AssignIfNullish<T, U> = { [P in keyof T]: T[P] extends null | undefined ? // is value a subtype of null|undefined? U[P & keyof U]: // take the value from U T[P] // preserve the original }; // type Test = { a: "null"; b: "undefined"; c: 42; } type Test = AssignIfNullish< { a: null, b: undefined, c: 42 }, { a:"null", b:"undefined", c: 24 } >;

Patio de recreo

El resto es solo cuestión de afirmar el tipo de retorno de la función:

 const assignDefault = <T, U>(target: T, source: U) => { Object.keys(source).forEach((key) => { const prop = target[key as keyof T] if (typeof prop === 'undefined' || prop === null) { // Error: Type 'U[keyof U]' is not assignable to type 'T[keyof T]'. target[key as keyof T] = source[key as keyof U] } }); return target as AssignIfNullish<T, U>; }; assignDefault({ a:1,b:null } as const,{ b:42 } as const); // { a:1, b:42 }

Patio de recreo

Finalmente, hay un error más con el que lidiar:

Error: el tipo 'U[keyof U]' no se puede asignar al tipo 'T[keyof T]'.

El compilador verifica si U[keyof U] ( source[key] ) es asignable a T[keyof T] ( target[key] ), pero no tiene ninguna información sobre cómo se relacionan T y U Todo lo que sabe es que ambos son parámetros de tipo genérico y, por lo tanto, pueden ser cualquier cosa. De acuerdo con la firma, ni siquiera se garantiza que sea un objeto ( lo sabe pero no el compilador):

 assignDefault(true, false); // no objection from the compiler

Dado que en este caso usted sabe más que el compilador, está bien usar as unknown as T[keyof T] para afirmar que source[key] es en realidad T[keyof T] :

 const assignDefault = < T extends object, U extends object >(target: T, source: U) => { Object.keys(source).forEach((key) => { const prop = target[key as keyof T] if (typeof prop === 'undefined' || prop === null) { target[key as keyof T] = source[key as keyof U] as unknown as T[keyof T]; } }); return target as AssignIfNullish<T, U>; }; assignDefault(true, false); // error as expected assignDefault({ a:null }, { a:42, b: "extra" }); // ok, { a:number }

Patio de recreo

Sin embargo, son muchas afirmaciones, ¿podemos hacerlo mejor? Sí, si renunciamos a las Object.keys a favor del bueno viejo for...in loop debido a la peculiaridad de que la key se escribe como string (hay una buena razón para ello, pero aún así). El uso for...in (con la protección adecuada) nos permite descartar las afirmaciones as keyof :

 const assignDefault = < T extends Partial<{ [P in keyof U]: unknown }> & object, U extends Partial<{ [P in keyof T]: unknown }> & object >(target: T, source: U) => { for (const key in source) { if (!Object.prototype.hasOwnProperty.call(source, key)) continue; const prop = target[key]; if (typeof prop === 'undefined' || prop === null) { Object.assign(target, key, { [key]: source[key] }); } } return target as AssignIfNullish<T, U>; };

Patio de recreo

Observe el uso de Object.assign(target, key, { [key]: source[key] }); para evitar el error de asignabilidad (también se podría usar Object.defineProperty(target, key, Object.getOwnPropertyDescriptor(source, key)!); ).

about 3 years ago · Santiago Trujillo Report
Answer question
Find remote jobs

Discover the new way to find a job!

Top jobs
Top job categories
Business
Post vacancy Pricing Our process Sales
Legal
Terms and conditions Privacy policy
© 2025 PeakU Inc. All Rights Reserved.

Andres GPT

Recommend me some offers
I have an error