zkldi
zkldi

Reputation: 94

Mandate that field on an interface is a member of an array in that same interface

How can I write an interface where one of the fields is a member of an array type in that same interface?

So far, I've got this:

interface Example<F extends string = string> {
  fruits: ReadonlyArray<F>;
  defaultFruit: F;
}

// or equivalently:
interface Example<F extends ReadonlyArray<string> = ReadonlyArray<string>> {
  fruits: F;
  defaultFruit: F[number];
}

But the problem with these is that they will allow things like:

const e: Example = {
  fruits: ["Apple", "Banana"],
  defaultFruit: "Something Else",
}

as F will extend itself to "Apple" | "Banana" | "Something Else".

I can do this explicitly, with something like

interface Example<F extends string> {
  fruits: ReadonlyArray<F>;
  defaultFruit: F;
}

const e: Example<"Apple" | "Banana"> = {
  fruits: ["Apple", "Banana"],
  defaultFruit: "Something Else", // <- correctly complains that this is not assignable to F
}

But I'd rather not write everything out twice. I'd like to be able to have:

const e: SomeInterface = {
  fruits: ["Apple", "Banana"],
  defaultFruit: "Something Else", // <- that disallows this.
}

Is there a way to automatically derive this? Thanks.

Upvotes: 1

Views: 35

Answers (1)

basarat
basarat

Reputation: 276165

as F will extend itself to "Apple" | "Banana" | "Something Else".

To prevent this, you should create a separate generate constrained by F.

Working example

interface Example<F extends string, D extends F> {
  fruits: ReadonlyArray<F>;
  defaultFruit: D;
}


function check<F extends string, D extends F>(value: Example<F, D>){return value}

const e = check({
  fruits: ["Apple", "Banana"],
  defaultFruit: "Something Else", // Error Type '"Something Else"' is not assignable to type '"Apple" | "Banana"'
});

Upvotes: 1

Related Questions