Search code examples
javascripttypescripttypes

how to change undefined type as optional in typescript?


I have created a custom route type for my project url. The function accepts two parameters,

  1. actual path url
  2. param for the url. eg) "/project/:projectId/user/:userId" => {projectId:"...",userId:"..."}

My second parameter (param) in the function can be undefined if there is no query path like project url in the code. But I find it difficult to add optional type when param type is undefiend. Anyone has better solution?

const projectDetailUrl = "/project/:id";
const projectUrl = "/project";

export type ExtractParams<T extends string> =
  T extends `${infer _Start}:${infer Param}/${infer _End}`
    ? Param | ExtractParams<_End>
    : T extends `${infer _Start}:${infer LastParam}`
    ? LastParam
    : never;

export type CreateParamObject<T extends string> = ExtractParams<T> extends
  | never
  | undefined
  ? undefined
  : {
      [K in ExtractParams<T>]: string;
    };


function push<T extends string>(url: T, params: CreateParamObject<T>) {
 console.log(url)
 console.log(params)
}


push(projectUrl,undefined)
push(projectDetailUrl,{id:"1"})

I want to removed undefiend without an error but keep params type if there is query path

AS IS 
push(projectUrl,undefined)
push(projectDetailUrl,{id:"1"})


TO BE
push(projectUrl)
push(projectDetailUrl,{id:"1"})

Solution

  • You can do that with generics and a conditional type using rest parameters.

    First infer the parameters of the URL with a generic parameter and your type ExtractParams. Since that type either returns a union of strings or never we can use a conditional type on it to check the result. If [ExtractParams<T>] extends [never] we know there are no params and return [{ [x: string]: string }?] to represent an optional object with string keys and string values. Otherwise we return the computed parameter object with [CreateParamObject<T>].

    See Optional parameters based on conditional types and microsoft/TypeScript/pull/24897 for more information about tuples in rest parameters and spread expressions.

    const projectDetailUrl = "/project/:id";
    const projectUrl = "/project";
    
    export type ExtractParams<T extends string> =
      T extends `${infer _Start}:${infer Param}/${infer _End}`
        ? Param | ExtractParams<_End>
        : T extends `${infer _Start}:${infer LastParam}`
        ? LastParam
        : never;
    
    export type CreateParamObject<T extends string> = {
      [K in ExtractParams<T>]: string;
    };
    
    function push<T extends string, U extends ExtractParams<T>>(
      url: T,
      ...params: [U] extends [never]
        ? [{ [x: string]: string }?]
        : [CreateParamObject<T>]
    ) {
      console.log(url);
      console.log(params);
    }
    
    push(projectUrl); // okay
    push(projectUrl, { whatever: "1" }); // okay
    push(projectDetailUrl, { id: "1" }); // okay 
    push(projectDetailUrl); // error 
    

    TypeScript Playground