If you plan to check a union-typed value with a switch/case statement, you should probably make it a disciminated union where the type property of each constituent of the union is declared to be the relevant string literal instead of just string. You don't really need conditional types to deal with this, at least not inside your someFunction() implementation.
For example, let's say your types look like this:
type MyType1 = { type: "type1", data: { a: string, b: number } };
type MyType2 = { type: "type2", data: { c: boolean, d: string } };
type MyType3 = { type: "type3", data: { e: number, f: boolean } };
type MyComplexType = MyType1 | MyType2 | MyType3;
Then the compiler will automatically treat checks on MyComplexType["type"] as a type guard, like this:
const exhaustivenessCheck = (x: never) => x;
function someFunction(item: MyComplexType) {
switch (item.type) {
case "type1":
console.log(2 * item.data.b); // okay
break;
case "type2":
console.log(item.data.d.charAt(0)); // okay
break;
case "type3":
console.log(7 - item.data.e); // okay
break;
default:
throw exhaustivenessCheck(item); // okay
}
}
That exhaustivenessCheck() is basically a throw statement if the function somehow falls through to default. That shouldn't happen, but the usefulness is that the compiler will warn you if it doesn't think you checked everything. That's because exhaustivenessCheck() requires its parameter to be of type never, which can't happen. If you comment out the case "type3" clause, or sometime later add a new constituent to the MyComplexType union, the exhaustivenessCheck() line will throw an error saying you failed to check a case.
At this point you could stop, but if your types are really that programmatic in that they contain just two properties, a type discriminant string and a data property, then you can define your types with less repetition like this:
// a mapping from type string to data type
type MyTypes = {
type1: { a: string, b: number };
type2: { c: boolean, d: string };
type3: { e: number, f: boolean };
}
// convert the mapping to the union of types
type MyType<K extends keyof MyTypes = keyof MyTypes> = {
[P in K]: { type: P, data: MyTypes[P] }
}[K]
You can verify that MyType or MyType<keyof MyTypes> expands to the MyComplexType union I defined above. Your old MyType1 is now MyType<"type1">, and so forth. That is, if you need to use your old names for the types you can do it like this:
type MyType1 = MyType<"type1">;
type MyType2 = MyType<"type2">;
type MyType3 = MyType<"type3">
type MyComplexType = MyType;
Hope that helps; good luck!