2

So I'm trying to get this behavior:

interface A {
    prop1: string;
    prop2: number;
    prop3: boolean;
}

type PropertyArray<T> = magical code

// PropertyArray<A> should be the same as the type ['prop1', 'prop2', 'prop3']

const properties: PropertyArray<A> = ['prop1', 'prop2', 'prop3'];

const fail1: PropertyArray<A> = ['prop1', 'prop2']; // type error
const fail2: PropertyArray<A> = ['prop1', 'prop1', 'prop2', 'prop3']; // type error

The point of this code is that if I add a new field the interface A, I also need to include that property to the array. If any of the properties is missing the build fails.

Thanks for the help.

4
  • Possible duplicate of How to transform union type to tuple type Commented Apr 28, 2019 at 19:28
  • The question linked above is applicable here if you operate on keyof A. The caveat there applies here: you can't really rely on the order of keys in an object type. That is, {a: string, b: number} is the same type as {b: number, a: string}. So would you want ['a','b'] or ['b','a']? Or do you want ['a','b'] | ['b','a'] (the union of all permutations of keys)? Commented Apr 28, 2019 at 19:30
  • yes @jcalz, you are right it's a duplicate, the answers in that link is what i wanted, thanks! Commented Apr 28, 2019 at 19:34
  • Possible duplicate of How to transform union type to tuple type Commented Apr 29, 2019 at 3:46

3 Answers 3

1

If you really care about property order then this is essentially a duplicate of this question about turning a union into a tuple if you define

type PropertyArray<T> = TuplifyUnion<keyof T>

But hopefully you don't really care about property order, and ['prop2', 'prop1', 'prop3'] would be an acceptable value of properties. In that case, there are two ways I can think of doing this:


One is to actually calculate PropertyArray<T> as a union of all possible permutations of keys in tuples, as you asked. This would naturally involve a circular conditional type which is not currently supported. I can instead make a definition that supports types with up to some fixed number of properties, like this:

type PropertyArray<T> = Tup<keyof T>
type Cons<H, T extends any[]> = T extends any ? ((h: H, ...t: T) => void) extends ((...r: infer R) => void) ? R : never : never
type Tup<U, V = U> = [U] extends [never] ? [] : U extends any ? Cons<U, Tup1<Exclude<V, U>>> : never
type Tup1<U, V = U> = [U] extends [never] ? [] : U extends any ? Cons<U, Tup2<Exclude<V, U>>> : never
type Tup2<U, V = U> = [U] extends [never] ? [] : U extends any ? Cons<U, Tup3<Exclude<V, U>>> : never
type Tup3<U, V = U> = [U] extends [never] ? [] : U extends any ? Cons<U, Tup4<Exclude<V, U>>> : never
type Tup4<U, V = U> = [U] extends [never] ? [] : U extends any ? Cons<U, Tup5<Exclude<V, U>>> : never
type Tup5<U, V = U> = [U] extends [never] ? [] : U extends any ? Cons<U, Tup6<Exclude<V, U>>> : never
type Tup6<U, V = U> = [U] extends [never] ? [] : U extends any ? Cons<U, Tup7<Exclude<V, U>>> : never
type Tup7<U, V = U> = [U] extends [never] ? [] : U extends any ? Cons<U, Tup8<Exclude<V, U>>> : never
type Tup8<U, V = U> = [U] extends [never] ? [] : U extends any ? Cons<U, Tup9<Exclude<V, U>>> : never
type Tup9<U, V = U> = [U] extends [never] ? [] : U extends any ? Cons<U, TupX<Exclude<V, U>>> : never
type TupX<U> = [] // bail out

And for your case:

interface A {
    prop1: string;
    prop2: number;
    prop3: boolean;
}

const properties: PropertyArray<A> = ['prop1', 'prop2', 'prop3'];
const fail1: PropertyArray<A> = ['prop1', 'prop2']; // type error
const fail2: PropertyArray<A> = ['prop1', 'prop1', 'prop2', 'prop3']; // type error

This works how you want, but is a lot of work for the compiler and could be brittle.


A slightly less crazy solution (which is still kind of crazy) is to use a helper function instead of a type alias. The helper function will only compile if its parameters include each and every key of the relevant object type exactly once:

type TupleHasRepeats<T extends any[]> = { [I in keyof T]: T[I] extends T[Exclude<keyof T, keyof any[] | I>] ? unknown : never}[number] 

const propertyArray = <T>() => <A extends Array<keyof T>>(...a: A & (keyof T extends A[number] ? unknown : never) & (unknown extends TupleHasRepeats<A> ? never : unknown )) => a;

And then try it:

interface A {
    prop1: string;
    prop2: number;
    prop3: boolean;
}

const propertyArrayA = propertyArray<A>();

const properties = propertyArrayA('prop1', 'prop2', 'prop3');
const fail1 = propertyArrayA('prop1', 'prop2'); // type error;
const fail2 = propertyArrayA('prop1', 'prop1', 'prop2', 'prop3'); // type error

That works also. I'd probably use the latter if I had to do anything in production code.


Okay, hope that helps; good luck!

Sign up to request clarification or add additional context in comments.

Comments

1

Use keyof:

type PropertyArray = Array<keyof A>; // 'prop1' | 'prop2' | 'prop3'
type PropertyArray<T> = Array<keyof T>;

1 Comment

keyof is not what I want, this is a duplicate, the answer is here How to transform union type to tuple type, thanks anyway :)
0

If you need an array of A interface then simply do the following:

const properties: A[] = [ { 'string', 0, true }, { 'string', 1, false } ];

Let me know if I understand your question right.

Thanks

1 Comment

no, what I'm looking for is in this link How to transform union type to tuple type but thanks anyway :)

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.