typescript - Hur kan jag filtrera en union baserat på närvaron eller frånvaron av en egenskap


Jag har en typ IResponse för ett API-svar sammanfogat med ELLER som varierar beroende på en URL-sökparameter.

här är hur jag avser att använda det

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"];

Jag får ett fel vid Pick<IResponse, "ca"> som säger Typen 'string' uppfyller inte begränsningen 'never'

Jag vill inte direkt referera till scheman C eller B eftersom deras schemanamn kan ändras, så jag behöver en typ som kommer att extrahera rätt typ för API-svaret från 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

Svar #1

Du kan använda Extract<T, U> och Exclude<T, U> hjälptyper för att filtrera unionstyper. Dessa typer tar en unionstyp T och behåller (för Extract) eller avvisar (för Exclude) alla medlemmar av T som kan tilldelas till U.

I ditt fall ser det ut som att du vill:

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

som producerar

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

som önskat.

Din version fungerade inte eftersom Pick<T, K> och Omit<T, K> hjälptyper filtrerar egenskaperna av typen T, och fungerar inte bra när T är en union eftersom de inte distribueras över unioner i T.

Playground-länk till kod