TypeScript: Indexering i en objekttyp med hjälp av keyof


Jag har ett inbäddat objekt efter nycklar som ser ut som följer:

const OBJECTS = {
    OBJECT1: {
        properties: {
            prop1: { value: "a" },
        },
    },
    OBJECT2: {
        properties: {
            prop2: { value: "b" },
        },
    },
} as const;

Jag försöker skapa en generisk typ där jag kan extrahera egenskapsvärdena för ett specifikt objekt - något som:

type PropertiesForObject<OBJECT_KEY extends keyof typeof OBJECTS> = {
    [PROPERTY_NAME in keyof typeof OBJECTS[OBJECT_KEY]["properties"]]: typeof OBJECTS[OBJECT_KEY]["properties"][PROPERTY_NAME]["value"];
};

Jag får dock följande felmeddelande:

Type '"value"' cannot be used to index type '{ readonly OBJECT1: { readonly properties: { readonly prop1: { readonly value: "a"; }; }; }; readonly OBJECT2: { readonly properties: { readonly prop2: { readonly value: "b"; }; }; }; }[OBJECT_KEY]["properties"][PROPERTY_NAME]'.

Finns det något sätt att indexera in i varje objekts egenskaper, givet att formen på egenskaperna är densamma? Jag skulle kunna tvinga det till ett gränssnitt, men jag vill behålla de starka typerna på värdena.

Svar #1

Detta kan mycket väl vara en bugg eller designbegränsning i TypeScript; kompilatorn känner inte igen att det generiska objektet kommer att ha en bokstavlig nyckel. Det är ett problem med den härledda begränsningen på typeof OBJECTS[K]["properties"][P]. Se microsoft/TypeScript#21760 för mer information (och ge det kanske en 👍 om du vill att det hanteras någon gång, men det ser inte lovande ut eftersom det har legat i vila i mer än två år från och med maj 2020).

Ett sätt att hantera detta är att göra typfunktionen mer generell (istället för typeof OBJECTS, använd en generisk typ T som uttryckligen begränsas till något med den struktur vi använder) och sedan specialisera den till typeof OBJECTS:

type GenericPropertiesForObject<
    T extends Record<K, { properties: { [k: string]: { value: any } } }>,
    K extends PropertyKey
    > = {
        [P in keyof T[K]["properties"]]: T[K]["properties"][P]["value"]
    };

type PropertiesForObject<K extends keyof typeof OBJECTS> =
    GenericPropertiesForObject<typeof OBJECTS, K>;

Detta kompilerar utan fel, även om det i princip utför samma beräkning. Låt oss se till att det fungerar:

type O1Props = PropertiesForObject<'OBJECT1'>;
// type O1Props = { readonly prop1: "a"; }

type O2Props = PropertiesForObject<'OBJECT2'>;
// type O2Props = { readonly prop2: "b"; }

Ser bra ut för mig. Okej, hoppas det hjälper; lycka till!

Lekplatslänk till kod