Search code examples
typescriptnode-csv-parse

How can I the take keys of some value from a type?


I tried to use csv-parse's option cast to convert the type.

My approach is as follows, but there are problems.

I referred to this answer: https://stackoverflow.com/a/60932900/19252706

Is it to possible to define KeysOfNumbers and KeysOfBooleans:

import {CastingFunction, parse} from 'csv-parse/browser/esm/sync';

const input =
  'ID,Type,From,Title,Content,Date,IsRead,IsAD\r\n1,0,Mars,My car glass was broken,How much DOGE to fix this.....,423042301654134900000,false,false';

type Mail = {
  ID: string;
  Type: number;
  From: string;
  Title: string;
  Content: string;
  Date: number;
  isRead: boolean;
  isAD: boolean;
};

// This is problem. Is this possible to fix?
type KeysOfNumbers<T> = string[];
type KeysOfBooleans<T> = string[];

const castNumberAndBoolean =
  <T>(
    keysOfNumbers: KeysOfNumbers<T>,
    KeysOfBooleans: KeysOfBooleans<T>,
  ): CastingFunction =>
  (value, context) =>
    keysOfNumbers.includes(context.column.toString())
      ? Number(value)
      : KeysOfBooleans.includes(context.column.toString())
      ? value === 'true'
        ? true
        : false
      : value;

parse(input, {
  columns: true,
  cast: castNumberAndBoolean<Mail>(['Type', 'Date'], ['isRead', 'isAD']),
});

Solution

  • This is a handy helper type:

    type KeysOfType<T, U> = {
        [K in keyof T]: T[K] extends U ? K : never
    }[keyof T];
    
    type TestA = KeysOfType<Mail, boolean>
    // 'isRead' | 'isAD'
    

    This type takes an object to map T, and a type for the properties to keep U. It iterates over each key K and checks if the type of that property T[K] extends U. If it does, keep the key, else throw it out by resolving to never. Lastly index the resulting object by its own keys to get all property types as a union.

    Now you can make types that use this type:

    type KeysOfNumbers<T> = KeysOfType<T, number>[]
    type KeysOfBooleans<T> = KeysOfType<T, boolean>[]
    
    type MailNumbers = KeysOfNumbers<Mail>
    //   ('Type' | 'Date')[]
    
    type MailBooleans = KeysOfBooleans<Mail>
    //   ('isRead' | 'isAD')[]
    

    Playground