Búsqueda de sitios web

typescript - ¿Cómo puedo filtrar una unión por la presencia o ausencia de una propiedad?


Tengo un tipo IResponse para una respuesta de API unido por OR que varía dependiendo de un parámetro de búsqueda en la URL.

aquí está cómo tengo la intención de usarlo

const data1 = await request<E<"aaa">>('/api/data/1?type=aaa');
const data2 = await request<E<"bbb">>('/api/data/1?type=bbb');

function request<T>(
  url: string
): Promise<T> {
  return fetch(url, config)
    .then((response) => response.json())
    .then((data) => data as T);
}

//index.ts

import {
  paths,
} from "./autoGeneratedTypes";

type IResponse =
  paths["/api/data/{id}"]["get"]["responses"]["200"]["content"]["application/json"];

export type E<T extends "aaa" | "bbb"> = T extends "aaa"
  ? Omit<IResponse, "ca">
  : Pick<IResponse, "ca">;

export type F = E<"aaa">["bb"];

Recibo un error en Pick<IResponse, "ca"> que dice Type 'string' does not satisfy the constraint 'never'

No quiero referenciar directamente los esquemas C o B ya que sus nombres de esquema pueden cambiar, así que necesito un tipo que extraiga el tipo correcto para la respuesta de la API desde IResponse.

//autoGeneratedTypes.ts
/**
 * This file was auto-generated by openapi-typescript.
 * Do not make direct changes to the file.
 */

export interface paths {
  "/api/data/{id}": {
    /** @description get the data */
    get: {
      responses: {
        /** @description Retrieved the data successfully */
        200: {
          content: {
            "application/json": components["schemas"]["D"];
          };
        };
      };
    };
  };
}

export interface components {
  schemas: {
    D: components["schemas"]["C"] | components["schemas"]["B"];
    C: {
      ca: components["schemas"]["B"][];
    };
    B: {
      ba: string;
      bb: components["schemas"]["A"][];
    };
    A: {
      aa: string;
      ab: string | null;
    };
  };
}

https://www.typescriptlang.org/play?ts=4.9.5#code/C4TwDgpgBAglC8UDeBYAUFKBDLAuKAzsAE4CWAdgOYDc6mWARvkWVVAD5TkCuANr7TQBfQelCQoAIQTI6UBnkIkKNOQyawA2gF1BI9GPDQAwjNQYoAY0WS9otOOgARGac62DaCAA8wAe2JgKEcoAFEAHgAVKB9gCHIAEwIoACIcLBSOVPUGFIA+GWjY+KTU9JS5AH4oAHkAW1JgcKcAGlTrfLl8AAVSSwBrZraUjrz7H39A4KMoAFksMDAVAgB1RoALY25iYnjLEBkItJx8zRT1FN10IA

Respuesta #1

Puedes usar los tipos de utilidad Extract<T, U> y Exclude<T, U> para filtrar tipos unión. Estos tipos toman un tipo unión T y, ya sea conservan (para Extract) o rechazan (para Exclude) cualquier miembro de T que sea asignable a U.

En tu caso parece que quieres:

type E<T extends "aaa" | "bbb"> =
  T extends "aaa" ? Exclude<IResponse, { ca: any }> : Extract<IResponse, { ca: any }>

que produce

export type F = E<"aaa">["bb"]; // okay
/* type F = {
    aa: string;
    ab: string | null;
}[] */

según se desee.

Tu versión no funcionó porque los tipos de utilidad Pick<T, K> y Omit<T, K> filtran las propiedades del tipo T, y no funcionan bien cuando T es una unión, ya que no se distribuyen sobre las uniones en T.

Enlace de Playground al código