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

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).

  • Are you looking for something like this link?

    – 

  • No, @jayatubi, that gives a union of the tuples, I want a tuple of the tuples. That way I can tail-recursively process them and move them into an output aggregator tuple.

    – 




  • 1

    I’m not sure how you can say both “I want a tuple” and “order is irrelevant”. Tuples have order. Just because you don’t care about the order doesn’t mean that there isn’t one. Can you articulate a reason why the caveats from the other questions don’t apply here? It seems exactly the same; people in those questions often say “I don’t care about the order” also.

    – 

  • @DavidDurschlag May be then you could try Union to Tuple on that result.

    – 

  • @jcalz — my goal is to be able to process one property at a time, so my conditional types can be tail-recursive. I don’t care what order they’re processed in. A union would also be fine if there was a way to take its “head/tail” the same way you can with a tuple (T extends [infer HEAD, ...infer TAIL]). Another way of saying this is “I want to process each property exactly once, in any order.” I know how to do that from a tuple of properties, but not from a union of properties. Perhaps something like T extends infer A|infer B? I have no idea how TS would handle that.

    – 




Leave a Comment