4

There are TONS of questions about conditional types based on a value. However I'm not sure can scenario below be implemented at all.

I'm getting acquainted with conditional types and I'm wondering if it's possible to make a conditional type that will be dependant on a non-empty string.

Let's consider next interface:

interface Animal {
    dogName: string;
    canBark: boolean;
}

and

interface AnimalToBeMapped {
    dogsNickname?: string;
    /// some other props
}

I'm using this interface to map an object

function mapAnimal(animal: AnimalToBeMapped): Animal {
    return {
        dogName: animal.dogsNickname,
        canBark: !animal.dogsNickname
    }
}

I'm setting canBark property to true only when the argument object has a dogName property set (should work also for empty string vs. non-empty string).

What I want to do is to add additional type-safety to my Animal interface (if someone is setting dogName manually to be defined then I want to force canBark property to be true and vise versa).

example:

const exmpl1: Animal = {
    dogName: 'Rex',
    canBark: false // I want an error here
}

and

const exmpl2: Animal = {
    dogName: '',
    canBark: true // An error here
}
2
  • Are you open for any workarounds? For instance, it is possible to make function which will accept two arguments (dogName and canBark)? Or type wirh generic argument? Commented Mar 27, 2021 at 17:42
  • Looks to me you are trying something that TypeScript was not designed for. TypeScript compiles to JavaScript, so any dynamic runtime behavior cannot be enforced with TypeScript typing features. The examples you provide contain hard-coded values and those will be checked just fine during compile time. But when I read something like "if someone is setting dogName manually to be defined then I want to force canBark property to be true and vise versa", I think about scenarios like handling user input or API fetch results. You're out of luck there. You need to write validation logic for that. Commented Mar 27, 2021 at 20:05

1 Answer 1

4

Please let me know if it works for you


type NonEmptyString<T extends string> = T extends '' ? never : T;

type WithName = {
  dogName: string,
  canBark: true,
}

type WithoutName = {
  dogName?: '',
  canBark: false
};

type Animal = WithName | WithoutName;


type Overloadings =
  & ((arg: { canBark: false }) => Animal)
  & ((arg: { dogName: '', canBark: false }) => Animal)
  & (<S extends string>(arg: { dogName: NonEmptyString<S>, canBark: true }) => Animal)

const animal: Overloadings = (arg: Animal) => {
  return arg

}

const x = animal({ dogName: '', canBark: false }) // ok
const xx = animal({ dogName: 'a', canBark: true }) // ok
const xxx = animal({ dogName: 'a', canBark: false }) // error
const xxxx = animal({ dogName: '', canBark: true }) // error
const xxxxx = animal({ canBark: true }) // error
const xxxxxx = animal({ canBark: false }) // ok

Playground

It is not so easy for TS to find distinguish between empty string and string. Type string is assignable to literal type '' (empty string), that's whu I used NonEmptyString helper

UPDATE If you are interested in a bit simplier solution, which is not requires extra funxtion, see this example:


type WithName = {
  dogName: string,
  canBark: true,
}

type WithoutName = {
  canBark: false
};

type Animal = WithName | WithoutName;

const x: Animal = {
  dogName: 'name',
  canBark: true
}; // ok

const xx:Animal={
  canBark:false
}; // ok

const xxx:Animal={
  canBark:true
} // expected error

const xxxx:Animal={
  dogName:''
} // expected error

Playground

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

2 Comments

Thanks for the answer. First of all, I would like to say that your answer is something next-levelish as usual! Awesome! However, after seeing this I’m thinking about simplifying this approach a bit… Do you think it can be done if instead of checking the property is non-empty string I will just make a property dogName optional? See we can just check if it exists then lock canBark to true. And if dogName does not exist canBark should be always false
@anotheruser, yes, you can stick with simple union. I made an update in my answer

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.