How to turn an object type into a list of name/type pairs at the type level in TypeScript?

44 Views Asked by At

Going from a list of name/type pairs to an object type is not particularly challenging:

type PropertySpec<K extends string|number|symbol, V> = readonly [K, V];
type UnknownProp = PropertySpec<string|number|symbol, unknown>;
type PropSet = ReadonlyArray<UnknownProp>;
type CreateObject<Props extends PropSet, OutObj extends Record<string|number|symbol, unknown> = {}> =
Props extends []
    ? OutObj
    : Props extends readonly [infer Prop, ...infer Props]
        ? Prop extends readonly [infer K, infer V]
            ? Props extends PropSet
                ? K extends string|number|symbol
                    ? CreateObject<Props, OutObj & Record<K, V>>
                    : readonly [`Bad Key`, K]
                : "Bad props"
            : readonly ["Bad prop", Prop]
        : "Bad prop list";

type Q1 = CreateObject<readonly [readonly ["a", string], readonly ["b", number]]>;
const q1a: Q1 = {a: "foo", b: 3}; // OK!
const q1b: Q1 = {a: "foo"}; // Correctly flags b as missing
const q1c: Q1 = {b: 3}; // Correctly flags a as missing
const q1d: Q1 = {a: "foo", b: 3, c: null}; // Correctly flags c as extraneous

Going the other direction, however, has me stumped. It seems like some variant of the classic "DON'T DO THIS" union-to-tuple conversion may be required (see e.g. How to transform union type to tuple type ; I think this has a number of threads on both SO and the TS github issues). Is this the only path? And am I right to think the caveats for why that path is undesirable m might not apply here (specifically because order is irrelevant)?

As context, this is useful when dealing with tail-recursive conditional types that include mapping over objects. While a tuple can be decomposed and handled one at a time building up an output buffer to implement tail recursion (and thus avoid the 50 stack size limit), mapped types don't seem to be able to handle this cleanly. Transforming an object type into a tuple would allow this, after which the transform back is easy (as seen above).

0

There are 0 best solutions below