Rafael Furtado
Rafael Furtado

Reputation: 85

Enforce a specific type of a key based on the value of another key in the object (Discriminated unions)

Maybe the title of the question wasn't so explicit what about I want, but what I want is something called discrimnated unions, I think. It's documented here: https://www.typescriptlang.org/docs/handbook/unions-and-intersections.html#discriminating-unions

A have a type A, with two fields, B and C, the type of the field B is well-defined set of string literals and I want that the type of C be in reason of the value from the field B.

Some code to better explain:

type Field1Values = "a" | "b" | "c";

type TypeForValueA = {
   ...
}

type TypeForValueB = {
   ...
}

type TypeForValueC = {
   ...
}

type RootType = {
    field1: Field1Values,
    field2: <something here that I didn't know exactly what to put>
}


const objA: RootType = {
    field1: "a",
    field2: {
        // Only the values from the type TypeForValueA are allowed
    }

}

const objB: RootType = {
    field1: "b",
    field2: {
        // Only the values from the type TypeForValueB are allowed
    }

}

How can I achieve the effect that I want?

Thanks 😃

Edit:

I already tried like in the docs

type NetworkState =
  | NetworkLoadingState
  | NetworkFailedState
  | NetworkSuccessState;

And the lint shows me all the fields from all the types, I want that only the fields from the correct type be shown

Upvotes: 0

Views: 805

Answers (1)

starball
starball

Reputation: 50254

I've created a playground link demonstrating an answer.

The problem with your original approach is that it doesn't communicate any mapping between the discriminating field's set of possible values and the discriminated field's possible set of types. All I did was adapt your example to follow the TypeScript docs you linked.

// type Field1Values = "a" | "b" | "c"; // no longer needed

type TypeForValueA = {
   fieldForA: string;
}

type TypeForValueB = {
   fieldForB: number;
}

type TypeForValueC = {
   fieldForC: boolean;
}

type RootA = {
    field1: "a";
    field2: TypeForValueA;
}
type RootB = {
    field1: "b";
    field2: TypeForValueB;
}
type RootC = {
    field1: "c";
    field2: TypeForValueC;
}
type RootType = RootA | RootB | RootC;


const objA: RootType = {
    field1: "a",
    // field2: press `ctrl+space` for suggestions
}
const objB: RootType = {
    field1: "b",
    // field2: press `ctrl+space` for suggestions
}
const objC: RootType = {
    field1: "c",
    // field2: press `ctrl+space` for suggestions
}

Upvotes: 1

Related Questions