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.

  • Possible duplicate of [How to transform union type to tuple type](https://stackoverflow.com/questions/55127004/how-to-transform-union-type-to-tuple-type) – jcalz Apr 28 '19 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)? – jcalz Apr 28 '19 at 19:30
  • yes @jcalz, you are right it's a duplicate, the answers in that link is what i wanted, thanks! – Tiago Fernandes Apr 28 '19 at 19:34
  • Possible duplicate of [How to transform union type to tuple type](https://stackoverflow.com/questions/55127004/how-to-transform-union-type-to-tuple-type) – Sean Vieira Apr 29 '19 at 03:46

3 Answers3

1

Use keyof:

type PropertyArray = Array<keyof A>; // 'prop1' | 'prop2' | 'prop3'
type PropertyArray<T> = Array<keyof T>;
pzaenger
  • 11,381
  • 3
  • 45
  • 46
  • keyof is not what I want, this is a duplicate, the answer is here [How to transform union type to tuple type](https://stackoverflow.com/questions/55127004/how-to-transform-union-type-to-tuple-type), thanks anyway :) – Tiago Fernandes Apr 28 '19 at 19:37
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!

jcalz
  • 264,269
  • 27
  • 359
  • 360
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

Murtaza Hussain
  • 3,851
  • 24
  • 30
  • no, what I'm looking for is in this link [How to transform union type to tuple type](https://stackoverflow.com/questions/55127004/how-to-transform-union-type-to-tuple-type) but thanks anyway :) – Tiago Fernandes Apr 28 '19 at 19:41