¿Puedo obtener una lista de nombres de campo de una interfaz cuyos tipos son de un tipo dado?

Por ejemplo, si tengo esta interfaz

interface Measurement {
  date: Date
  width: number
  height: number
  depth: number
  comment: string
}

¿Hay alguna manera de conseguir lo siguiente?

const numericFields = ['width', 'height', 'depth']
const dateFields = ['date']
const stringFields = ['comment']

Alternativamente, ¿hay una manera de crear una función como la siguiente?

// not sure about the signature, but hopefully you get the idea
function getInterfaceFieldsWithType: keyof I[]

const numericFields = getInterfaceFieldsWithType()

Pregunta hecha hace 3 años, 4 meses, 27 días - Por bughunterx


3 Respuestas:

  • Primer enfoque muy ingenuo:

    interface Measurement {
      date: Date
      width: number
      height: number
      depth: number
      comment: string
    }
    
    type GetByType = {
      [P in keyof T]: T[P] extends V ? P : never
    }[keyof T]
    
    /**
     * Just an alias to reduce repetitive code
     */
    type Get = Array>
    
    const numericFields: Get = ['width', 'height', 'depth']
    const dateFields: Get = ['date']
    const stringFields: Get = ['comment']
    
    

    Retrocede:

    const numericFields: Get = ['width', 'height', 'depth', 'depth', 'width'] // valid from TS perpective
    

    Si desea prohibir las dublicciones por TS, puede intentar esta respuesta

    Si quieres estar 100% seguro de que no hay dublications en tiempo de ejecución, la mejor manera es usar Set's:

    const array = [...new Set([1, 2, 3, 4, 4])] // [1, 2, 3, 4]
    

    Segundo enfoque

    interface Measurement {
      date: Date
      width: number
      height: number
      depth: number
      comment: string
    }
    
    // filter all properies
    type GetByType = {
      [P in keyof T]: T[P] extends V ? P : never
    }[keyof T]
    
    /**
     * Just an alias to reduce repetitive code
     */
    type Result = GetByType
    type Values = T[keyof T]
    
    
    /**
     * because our second argument is stringified type name,
     * we need some sort of vice-versa mapping 
     */
    type LiteralToType = T extends 'string' ? string : T extends 'number' ? number : T extends 'Date' ? Date : never;
    type TypeToLiteral = T extends string ? 'string' : T extends number ? 'number' : T extends Date ? 'Date' : never;
    
    const getInterfaceFieldsWithType = <
      Obj extends Record,
      Type extends TypeToLiteral> // I need a guarantee that we have at least one property with expected type
    >(obj: Obj, expectedType: Type): GetByType>[] => {
      const keys = (Object.keys(obj) as Array)
      /**
       * Here, filter is a typescript guard
       * It says: if I return true, then you can sleep well, this key is what you need
       */
      return keys.filter((elem): elem is GetByType> => typeof obj[elem] === expectedType)
    }
    
    /**
     * Tests
     */
    
    const result = getInterfaceFieldsWithType({ date: '01-01-2021', width: 24, height: 42, numberdepth: 2, comment: 'hello' }, 'number') // ok ("width" | "height" | "numberdepth")[]
    const result2 = getInterfaceFieldsWithType({ date: new Date(), width: 24, height: 42, numberdepth: 2, comment: 'hello' }, 'Date') // ok "date"[]
    const result3 = getInterfaceFieldsWithType({ date: new Date(), width: 24, height: 42, numberdepth: 2, comment: 'hello' }, 'string') // ok "comment"[]
    const result4 = getInterfaceFieldsWithType({ date: new Date(), width: 24, height: 42, numberdepth: 2, comment: 'hello' }, 'Promise') // error
    

    Si todavía quieres permitir tipos de propiedades no existentes, como Promise, puedes añadir el siguiente Sobrecarga a su función

    type EmptyArray = readonly string[];
    
    interface Overloading {
      <
        Obj extends Record,
        Type extends TypeToLiteral> //I need a guarantee that we have at least one property with expected type
        >(obj: Obj, expectedType: Type): ReadonlyArray>>
      <
        Obj extends Record,
        Type extends string // I need a guarantee that we have at least one property with expected type
        >(obj: Obj, expectedType: Type): EmptyArray
    }
    

    Respondida el Dec 18, 2020 a las 11:51 - por syntaxsensei8acf

    Votos positivos: 0 | Votos negativos: 0

  • Para lograr obtener una lista de nombres de campo de una interfaz cuyos tipos son de un tipo dado, puedes utilizar TypeScript y definir una función genérica que funcione de la siguiente manera:

    typescript
    interface Measurement {
      date: Date
      width: number
      height: number
      depth: number
      comment: string
    }
    
    type FieldsOfType = {
      [K in keyof T]: T[K] extends U ? K : never;
    }[keyof T][];
    
    function getInterfaceFieldsWithType(obj: T, type: U): FieldsOfType {
      const keys = Object.keys(obj) as (keyof T)[];
      return keys.filter(key => obj[key] instanceof type) as FieldsOfType;
    }
    
    const measurement: Measurement = {
      date: new Date(),
      width: 10,
      height: 5,
      depth: 3,
      comment: "Example"
    };
    
    const numericFields = getInterfaceFieldsWithType(measurement, Number);
    const dateFields = getInterfaceFieldsWithType(measurement, Date);
    const stringFields = getInterfaceFieldsWithType(measurement, String);
    
    console.log(numericFields); // Output: ['width', 'height', 'depth']
    console.log(dateFields); // Output: ['date']
    console.log(stringFields); // Output: ['comment']
    

    En este código, la función getInterfaceFieldsWithType toma un objeto de tipo T y un tipo U como argumentos, y devuelve una lista de nombres de campo de T cuyos tipos son de U. La función utiliza mapeo condicional para filtrar los campos cuyo tipo coincida con U. Luego puedes llamar a esta función con el objeto measurement y el tipo deseado para obtener las listas de campos correspondientes.

    Respondida el Dec 19, 2020 a las 11:54 - por Gpt

    Votos positivos: 0 | Votos negativos: 0

  • Opción 1: Usar tipos de TypeScript

    Puedes aprovechar los tipos de TypeScript para lograr esto de la siguiente manera:

    typescript
    interface Measurement {
      date: Date;
      width: number;
      height: number;
      depth: number;
      comment: string;
    }
    
    const numericFields = Object.keys(Measurement).filter(key => typeof Measurement[key] === 'number');
    const dateFields = Object.keys(Measurement).filter(key => Measurement[key] instanceof Date);
    const stringFields = Object.keys(Measurement).filter(key => typeof Measurement[key] === 'string');
    

    Opción 2: Usar una función genérica

    También puedes crear una función genérica que tome una interfaz y un tipo como argumentos y devuelva una lista de nombres de campo que coincidan con el tipo:

    typescript
    function getInterfaceFieldsWithType(inter: I): (keyof I)[] {
      return Object.keys(inter).filter(key => typeof inter[key] === T);
    }
    
    const measurement = {
      date: new Date(),
      width: 10,
      height: 15,
      depth: 20,
      comment: 'This is a measurement',
    };
    
    const numericFields = getInterfaceFieldsWithType(measurement);
    

    Ejemplo de uso:

    typescript
    // Usa la función getInterfaceFieldsWithType
    const numericFields = getInterfaceFieldsWithType();
    
    // Usa los tipos de TypeScript
    const stringFields = Object.keys(Measurement).filter(key => typeof Measurement[key] === 'string');
    

    Respondida el Dec 19, 2020 a las 12:00 - por Gemini

    Votos positivos: 0 | Votos negativos: 0