1

Consider the following code

type EnforceNonEmptyValue<TValueArg extends string> = {
  value: TValueArg extends '' ? never : TValueArg
  type: 'substringMatch'
}

function identity<
  TValueArg extends string
>(
  object: EnforceNonEmptyValue<TValueArg>
) {
  return object
}

// rightfully complaints
identity({value: '', type: 'substringMatch'})
//---------^
//  `Type 'string' is not assignable to type 'never'.ts(2322)`

// works
identity({value: 'works', type: 'substringMatch'})

The function identity with the help of EnforceNonEmptyValue generic enforces that the object passed to the function has a non empty string value, i.e. the value of value property should not be an empty string ''

I want to extend this check for an array of objects

identityArray([
  {
    type: 'substringMatch',
    value: 'np-payment',
  },
  {
    type: 'substringMatch',
    value: '',
    //------^ should complain here
  },
])

i.e I want typescript to throw an error if any object in an array of objects has a value property's value as an empty string ''

But I've been having a hard time to make it work. How could we enforce it for an array of objects? ( with or without an identity function )

Arihant
  • 477
  • 4
  • 18

1 Answers1

1

You can use a mapped array/tuple type as shown here:

function identityArray<T extends string[]>(
  args: [...{ [I in keyof T]: EnforceNonEmptyValue<T[I]> }]
) { }

The compiler can infer the type T from the homomorphic mapped type (see What does "homomorphic mapped type" mean?) {[I in keyof T]: EnforceNonEmptyValue<T[I]>}. I wrapped it with a variadic tuple type ([...+]) to give the compiler a hint that we'd like T to be inferred as a tuple type and not as an unordered array type, but you might not care either way.

Anyway, let's test it:

identityArray([
  {
    type: 'substringMatch',
    value: 'np-payment',
  },
  {
    type: 'substringMatch',
    value: '', // error!
  },
])
// function identityArray<["np-payment", ""]>(
//   args: [EnforceNonEmptyValue<"np-payment">, EnforceNonEmptyValue<"">]
// ): void

Looks good. The compiler accepts "np-payment" and rejects "". You can see that T is inferred as the tuple type ["np-payment", ""] and the args argument is of the mapped tuple type [EnforceNonEmptyValue<"np-payment">, EnforceNonEmptyValue<"">].

Playground link to code

jcalz
  • 264,269
  • 27
  • 359
  • 360