Home > Blockchain >  TypeScript extend from any string but override with type
TypeScript extend from any string but override with type

Time:07-14

I have a function that checks to see if a generic type’s value exists in an array:

export function isValueInArray<T extends string>(
  values: T[],
  value: T | undefined
) {
  return value ? values.includes(value) : false;
}

It would then be used like this:

export type Status = 'OPEN' | 'CANCELED' | 'ACCEPTED' | 'INVALIDATED';

// typeof buyNowStatus === Status 
const isValid = isValueInArray(['OPEN'], buyNowStatus)

In this case it works in terms of auto-completing the first array argument. The issue is that we can pass in any string:

// no typescript error is thrown here 
const shouldBeInvalid = isValueInArray(['OPEN', 'ANY'], buyNowStatus)

One way to fix this would be to pass the Status in as a generic:

// ts errors out properly (but we must pass in a generic)
const isInvalid = isValueInArray<Status>(['OPEN', 'ANY'], buyNowStatus)

Is there a way to achieve this without needing to pass Status in as a generic? To somehow infer the Status type from buyNowStatus and for that to override the default T extends string behaviour in isValueInArray?

function checkStatus(status: Status) {
  // no typescript error thrown here either
  return isValueInArray(['ANY'], status);
}

CodePudding user response:

You need to be generic over the whole array, and then value is typed as a derivation of that array.

You also need to make sure the array is the right type. A literal ['a', 'b'] will be inferred as string[]. BUt you want something like Status[].

For example:

export function isValueInArray<T extends string[]>(
  values: T,
  value: T[number] | undefined
) {
  return value ? values.includes(value) : false;
}

export type Status = 'OPEN' | 'CANCELED' | 'ACCEPTED' | 'INVALIDATED';
const statuses: Status[] = ['OPEN', 'ACCEPTED']

isValueInArray(statuses, 'OPEN') // fine
isValueInArray(statuses, 'foo') // error

See playground


Now I assumed that you wanted to ensure value is a valid member type of values. But it sounds like you want to ensure that the array values only has strings that match the type of value.

You can do that, too:

export function isValueInArray<
  Value extends string,
  Arr extends Value[]
>(
  values: Arr,
  value: Value | undefined
) {
  return value ? values.includes(value) : false;
}

export type Status = 'OPEN' | 'CANCELED' | 'ACCEPTED' | 'INVALIDATED';

declare const status: Status | undefined

isValueInArray(['CANCELED'], status) // fine
isValueInArray(['ANY'], status) // error

Now there are two generics. Value is the type of the second argument. And then Arr is the first argument that must be an array of the Value type.

See playground

  • Related