1

I would like to create a TypeScript type which is checking if an element COULD be a string or not.

That means that this element could have the type "string" or "any", but not "number", "boolean", "number[]", "Person", etc.

I tried with conditional types, but I can't find how to exclude "all which is impossible to be a string".

I tried too the type Exclude<any, string>, but that's not exactly what I want.

EDIT

I explain better my problem : I create a static method foo(data) which will do something and return a string if data is a string or do something else and return a number if data is not a string.

For that, it's simple, I can do an overload like this:

class FooClass {

    static foo(data: string): string
    static foo(data: any): number
    static foo(data: any): string | number {
        if (typeof data === 'string') {
            return 'a';
        } else {
            return 2;
        }
    }
}

Now, assume that the foo() method will throw an Error if data is not a string. I have two cases :

  • If the user of the method don't know the type of the data (which could be a string), I want that he can use this method and I will return an Error if it's not a string.

  • If the user knows the type of the data (a number, for example), I want to tell him that it's impossible, it can't be a string. I want to tell that on type checking (error displayed in the IDE), not at runtime.

class FooClass {

    static foo(data: string): string
    static foo(data: NotAString): Error
    static foo(data: any): string | Error {
        if (typeof data === 'string') {
            return 'a';
        } else {
            return new Error();
        }
    }
}
let foo1: string;
const bar1: string = FooClass.foo(foo1);  // No error
let foo2: any;
const bar2: string = FooClass.foo(foo2);  // No error
let foo3: number;
const bar3: string = FooClass.foo(foo3);  // Error (displayed on the IDE)
let foo4: Bar;
const bar4: string = FooClass.foo(foo4);  // Error (displayed on the IDE)

I'd like to know how to define the type NotAString (or its contrary, CouldBeAString).

Any idea ?

Thanks !

3
  • An element can be any but not "number", "boolean", "number[]", "Person", etc? Do you understand the meaning of any? Wwhat is "etc."? You need either a complete whitelist or a complete blacklist but it looks like you have neither. The whitelist "string" or "any" means that all types are allowed. Commented Feb 23, 2021 at 16:40
  • Why isn't 'string' enough? I don't understand the difference you are trying to achieve from your examples Commented Feb 23, 2021 at 16:42
  • Thanks for your first answers. I think that my question was not clear, because the responses are not doing exactly what I want. I edited my post and added a concrete example of my problem, hoping it will be more clear. And please, could you remove your -1 ? I think that now my question is correct and make sense. Thanks ! 😊 Commented Feb 23, 2021 at 20:58

2 Answers 2

6

You seem to be confusing the type system with functional checks here.

In case you need to check at runtime if something is a string, you would use the same logic as in normal JS.

Runtime check on a property

if (typeof something === 'string') { ... }

If we are talking about the data you get from an API or something like that, you would always need to use something like this, because type system will not do automatic runtime checks for you.

Conditional type thinning

In case you need to create a conditional type that allows you to do type thinning on a parameter there are a few ways to go about it. However, one thing to note here is the following:

  • the unknown is the default type in current versions of TS, not any
  • unknown and any means any valid data type in the language. You cannot say something is unknown but not number or boolean.
  • If you don't know what something is use unknown
  • If you know it is an object, but not sure what props it has you can define it as Record<string, unknown>

For type thinning, you can use a combination of a generic property and extends keyword.

type Result<T> = T extends string
  ? A
  : T extends boolean
  ? B
  : T extends number
  ? C
  : never;

Where A, B, C are result types based on the generic type.

If a generic parameter is not explicitly set, the incoming prop is of unknown type by default. You can also scope it to a few values that your entry parameter can be in to have more granular control.

Sign up to request clarification or add additional context in comments.

3 Comments

Hi, thanks for your answer. That's not exactly what I want to do, so I edited my post to be more clear. Thanks for your help ! :)
I will gladly update the answer. Thanks for clearing up the confusion
I hadn't thought about using ternary in this way, to find a type, and I'm glad this answer is still up
0

NotAString is easy:

type NotAString = number | boolean | symbol | bigint | null | undefined | object

The only way to define CouldBeAString, on the other hand, is with a type parameter:

type CouldBeAString<S, R=S> = string extends S ? R : S extends string ? R : never

Now you can do:

function foo<S>(data: CouldBeAString<S>) { ... }

foo(1) // Error!
foo(true) // Error!
foo(false) // Error!
foo(['array']) // Error!

foo('1') // works ✅
foo(1 as string | number) // works ✅
foo(1 as unknown) // works ✅
foo(1 as '1' | 1) // works ✅

Comments

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.