1

I currently have an interface using a data member of an array of some other interface type, which can be determined by an enum:

enum E { a, b, c }
interface E2Data {
    [E.a]: { a: string },
    [E.b]: { b: string },
    [E.c]: { c: string }
}

interface A<T extends E[] = E[]> {
    data: { [i in keyof T]: T[i] extends E ? E2Data[T[i]] : never }
}

// Can give the enums in the template, and get autocomplete in the data.
let a1: A<[E.a, E.b]> = {
    data: [{ a: "" }, { b: "" }]
};

// Or can get autocomplete with type assertion on elements.
// Ideally this would infer template types too, but apparently not.
let a2: A = {
    data: [ <E2Data[E.a]>{ a: "" }, <E2Data[E.b]>{ b: "" } ]
}

According to my understanding, I can now use a1.data and a2.data as arrays, but a1.data knows the type of each element (so actually a tuple?)

Now I want to use a 2D array for interface A's data.

I thought the below would work, but it would not allow me to index E2Data using T[i][j].

interface B<T extends E[][] = E[][]> {
    data: {
        [i in keyof T]:
        T[i] extends E[] ? {
            [j in keyof T[i]]:
            T[i][j] extends E ? E2Data[T[i][j]] : never }
        //                      ^^^^^^^^^^^^^^^
        // Type 'T[i][j]' cannot be used to index type 'E2Data'.
        : never
    }
}

My current workaround is to use a type union, but this won't let me specify the data I want using a template.

interface B {
    data: (E2Data[E.a] | E2Data[E.b] | E2Data[E.c])[][];
}

Is there a way to support the 2 methods of autocomplete on data as described above for a 2D array?

let b1: A<[ [E.a], [E.b] ]> = {
    data: [ [{ a: "" }], [{ b: "" }] ]
}

let b2: A = {
    data: [ [<E2Data[E.a]>{ a: "" }], [ <E2Data[E.b]>{ b: "" }] ]
}
2
  • Do the individual arrays in B.data contain a combination of different data types, or are they grouped by type? If you just want, for example, an array of E.a data and an array of E.b data then that is easy. All you need to do is replace E2Data[T[i]] with E2Data[T[i]][] in interface A. But I suspect you need to mix types or else you wouldn't have posted this. Commented Sep 25, 2020 at 21:09
  • The desired type of data was like in the workaround interface B . Your answer below covered that anyway! Commented Sep 28, 2020 at 14:57

1 Answer 1

1

This took me some thinking, but the solution is actually pretty straightforward because you've already done most of the legwork with your original interface A.

Define a generic type DataArray which takes a tuple of E values as its generic and returns a tuple of the corresponding E2Data. You could reformat so that DataArray contains the logic and A uses it, but for simplicity let's just access the existing logic from A.

type DataArray<T extends E[]> = A<T>['data'];

Instead of using a nested index signature in interface B, you can use a single index signature and call on the DataArray type to create the inner arrays.

interface B<T extends E[][] = E[][]> {
    data: {
        [i in keyof T]: T[i] extends E[] ? DataArray<T[i]> : never
    }
}

Now you create any 2D data type by mixing and matching E.

let b: B<[[E.a], [E.b, E.c]]> = {
    data: [ [{ a: "" }], [{ b: "" }, {c: ""}] ]
}

Edit I know you didn't ask for this, but here's how to handle more deeply nested values. (With some guidance from this answer about recursive arrays).

interface Nested<T> extends Array<Nested<T> | T> {
}

type NestedData<T extends Nested<E>> = { 
    [i in keyof T]: T[i] extends E ? E2Data[T[i]] : T[i] extends Nested<E> ? NestedData<T[i]> : never
}

interface C<T extends Nested<E>> {
    data: NestedData<T>
}

let c: C<[E.a, [E.b, [E.c]]]> = {
    data: [ { a: "" }, [{ b: "" }, [{c: ""}] ]]
}
Sign up to request clarification or add additional context in comments.

Comments

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.