Búsqueda de sitios web

javascript - hacer cumplir que el parámetro de la función debe pertenecer al tipo genérico T


En mi función de TypeScript quiero hacer cumplir que todas las claves del segundo argumento deben pertenecer al objeto del primer argumento.

algo similar a esto

mapTo([new KeyValue('a', 'b'), new KeyValue('x', 'y')], {key: {key2: 1}});

en esto todas las claves del segundo argumento (key y key2) deben pertenecer a las claves del objeto KeyValue

aquí KeyValue es :

class KeyValue {
  key: string;
  key2: string;

  constructor(k, k2) {
    this.key = k;
    this.key2 = k2;
  }
}

gustar

mapTo([new KeyValue('a', 'b')], {key: {key2: 1}}); -> bien

mapTo([new KeyValue('a', 'b')], {key: {key3: 1}}); -> error/key3 no válido

Para esto he implementado la función mapTo

public nest<T, K extends keyof T, K2 extends keyof T>(arr: [T], basis: {[k in K]: {[k2 in K2]}}) {
    console.log('Hello!!!');
    console.log(basis);
  }

este código funciona perfecto pero si agrego otra clave en la clase KeyValue y paso esta clave como argumento, así:

mapTo([new KeyValue('a', 'b', 'c')], {key: {key2: {key3: 1}}});

y KeyValue a:

class KeyValue {
  key: string;
  key2: string;
  key3: string;

  constructor(k, k2, k3) {
    this.key = k;
    this.key2 = k2; 
    this.key2 = k3;
  }
}

entonces mi función implementada arriba no validaría la tercera clave

entonces, ¿cómo podría implementar la función anterior para que acepte valores anidados dinámicos y funcione perfectamente?

algunos ejemplos más:

mapTo([new KeyValue('a', 'b' ,'c')], {key: 1}); -> bien

mapTo([new KeyValue('a', 'b', 'c')], {key: {key1:1}}); -> bien

mapTo([new KeyValue('a', 'b', 'c')], {key1: {key:1}}); -> bien

mapTo([new KeyValue('a', 'b', 'c')], {key1: {key:{key3:1}}}); -> bien

mapTo([new KeyValue('a', 'b' ,'c')], {key1: {key:{key4:1}}}); -> error/key4 no existe

mapTo([new KeyValue('a', 'b', 'c')], {key3: {key2:0}); -> bien

Respuesta #1

Puedes usar un tipo mapeado recursivo para definir una estructura que permita que cualquier clave de T se especifique en cada nivel. Además, podemos asegurarnos de que ninguna clave se especifique dos veces en un camino, omitiendo la clave actual en cada nivel usando el tipo Omit:

type Diff<T extends string, U extends string> = ({ [P in T]: P } & { [P in U]: never } & { [x: string]: never })[T];
type Omit<T, K extends keyof T> = Pick<T, Diff<keyof T, K>>;


type Nested<T> = { [P in keyof T]?: Nested<Omit<T, P>> | number }


function mapTo<T>(arr: [T], basis: Nested<T>) {
    console.log('Hello!!!');
    console.log(basis);
}
class KeyValue {
    constructor(public key: string, public key1: string, public key3: string) {
    }
}

mapTo([new KeyValue('a', 'b', 'c')], { key: 1 }); //-> okay 
mapTo([new KeyValue('a', 'b', 'c')], { key: { key1: 1 } }); //-> okay 
mapTo([new KeyValue('a', 'b', 'c')], { key1: { key: 1 } }); //-> okay 
mapTo([new KeyValue('a', 'b', 'c')], { key1: { key: { key3: 1 } } }); //-> okay
mapTo([new KeyValue('a', 'b', 'c')], { key1: { key: { key4: 1 } } }); //-> error // key4 not exist
mapTo([new KeyValue('a', 'b', 'c')], { key: { key: 1 } }); //-> error, key appears twice