sindresorhus/is

Is it possible to assert that a variable is an array of strings?

papb opened this issue ยท 21 comments

papb commented

Is it possible to assert that a variable is an array of strings, ideally with automatic typescript type assertion to string[]? I could not find information about this in the readme. It does not seem possible.

If yes, this issue is a readme clarification request. If not, this issue is a feature request ๐Ÿ˜ฌ

papb commented

After a more cautious read, I found that assert.array<string>(foo) will:

  • Ensure foo is an array (of anything)
  • Upon success in the bullet above, make TS think the type is string[]

So I guess I could use for now:

assert.array<string>(foo);
for (const element of foo) {
  assert.string(element);
}

But it would be much better to have a simpler way to do it... ๐Ÿ˜

Would you be willing to accept a PR for this feature?

Note: I did not explicitly specify how exactly the feature will look like in purpose; I will think more carefully if you say yes. Ideally something very powerful that can take other assertions as an extra argument and auto-apply it on each element and at the same time be smart on the asserted types...

I played a bit in the typescript playground, and what we can do is overload (or change) assert.array to receive an assertion.

link to playground

Code snippet:

declare function assertString(value: unknown): asserts value is string;

declare function assertArray<T>(
  arr: unknown[],
  assertion: (value: unknown) => asserts value is T,
): asserts arr is T[];

// asd is unknown[]
const asd: unknown[] = [];

assertArray(asd, assertString);

// asd is string[]
console.log(asd);

So you're more than welcome to open a PR ๐Ÿ‘๐Ÿป

Like always, make sure to test your code, and include any relevant changes in the documentation.
Discuss with us if you have any questions about implementation or api.

@sindresorhus any thoughts on what's the best choice for the api? add overload? new method entirely?

papb commented

Hi @gioragutt, thanks for the fast response!! Very nice your work in the playground. I didn't know asserts could work that nicely.

Honestly neither did I, I just tried to see if the syntax is valid and it is ๐Ÿคท๐Ÿปโ€โ™‚๏ธ

Hurray for good syntax design ๐ŸŽ‰

Sounds like a good feature. It will need a more specific proposal on how the final API should look like though (whether it should be a separate method or overload, and why / why not).

@sindresorhus Can I take this PR? what do you think the API should look like?

It will need a more specific proposal on how the final API should look like though (whether it should be a separate method or overload, and why / why not).

assert.arrayOfType(arr, is.string) with the same signature as what @gioragutt suggested.

Is this ok?

I chose the name since typedArray naming is already taken and references something else.

Another option is assert.arrayItems(arr, is.string) which doesn't imply that it's only meant for checking types.

papb commented

Hi @Arnovsky, thanks for being willing to fix this! I will use this extensively.

Among arrayOfType and arrayItems, I prefer arrayItems since as you said we can check things like evenInteger which is not a type per se.

But what do you think of just overloading the already existing array method? Since currently is.array and assert.array only expect one parameter, if you were to add a second parameter it wouldn't be an issue:

is.array(arr, is.string)
assert.array(arr, assert.string)
// or maybe
assert.array(arr, is.string)

@papb I like your idea, overloading seems like a better API than I suggest , the only question is if it's explicit enough.

papb commented

Hmm, I see... Of course I am probably biased since I suggested the API, but I do not see any other possible interpretation for is.array(arr, is.string) other than "array whose elements satisfy is.string". Is it possible that someone would interpret it in another way?

If I had to suggest a name instead of an overload, I would say .arrayWhoseElementsSatisfy, but that is too long...

@papb is.array(arr, is.string) Sounds good to me. Is this fine with you? @sindresorhus

papb commented

@Arnovsky and how do you feel about assert? assert.array(arr, is.string) or assert.array(arr, assert.string)? I would be fine with both I guess, whichever is easier to implement is ok to me.

Depends on the use case, but IMO both are needed.
I think assert.array(arr, assert.string) is more consistent since we are asserting and not returning anything. To me it makes more sense API wise.

papb commented

Looks good to me, is.array(arr, is.string) and assert.array(arr, assert.string) then ๐Ÿ˜ƒ

Let's see what @sindresorhus says

@papb @Arnovsky this api looks good to me ๐Ÿ‘๐Ÿป

๐Ÿ‘

@papb @gioragutt After starting to implement I have realized that is.array is a property (same applies for assert.array) and it cannot be overloaded.

So I'll implement the arrayItems method instead.

papb commented

@Arnovsky I don't understand, I don't see why it wouldn't be overloadable, can you please clarify? Maybe I can help you figure out how to overload them

papb commented

For example:

- is.array = Array.isArray;
+ is.array = (value: unknown, elementChecker?: Checker) => {
+   if (!elementChecker) {
+     return Array.isArray(value);
+   }
+   // ... your implementation
+ }

@papb Yes I have just realized this, thank you!