charleszardo
charleszardo

Reputation: 189

Typescript: The return type of a function to be the type of one property from multiple types

declare type Foo = {
  fieldA?: 'a' | 'b' | 'c';
  fieldB?: SomeOtherType;
  fieldC?: number;
  fieldD?: string;
  fieldE?: boolean;
}

declare type Bar = {
  fieldX?: boolean;
  fieldY?: 'hello' | 'world'
  fieldZ?: AndYetAnotherType;
}

const getPropertyFromFooOrBar = (
  property: string,
  foo: Foo,
  bar: Bar
): MISSING_TYPE => {
  return foo[property] || bar[property] || undefined;
};

I need the return type of the function (MISSING_TYPE above) to be a value from Foo or Bar (or undefined). Is there something similar to keyof Bar that can be used to handle this typing?

Upvotes: 0

Views: 40

Answers (2)

charleszardo
charleszardo

Reputation: 189

I ended up going with the following:

declare type Foo = {
  fieldA?: 'a' | 'b' | 'c';
  fieldB?: SomeOtherType;
  fieldC?: number;
  fieldD?: string;
  fieldE?: boolean;
}

declare type Bar = {
  fieldX?: boolean;
  fieldY?: 'hello' | 'world'
  fieldZ?: AndYetAnotherType;
}

type FooValue = Foo[keyof Foo];
type BarValue = Bar[keyof Bar];

const getPropertyFromFooOrBar = (
  property: string,
  foo: Foo,
  bar: Bar
): FooValue | BarValue | undefined => {
  return foo[property] || bar[property] || undefined;
};

Upvotes: 0

Daniel Gimenez
Daniel Gimenez

Reputation: 20494

You can determine the type of a property of combined types by creating a union type and using keyof that union type in a generic type declaration. That's a confusing sentence - here are the steps taken in the example below:

  • First, an union type called FooAndBar is declared. This isn't necessary, but it makes things more readable.
  • A generic type argument is created for the function that derives from the keys of the intersection type.
  • The property argument is declared using that generic type.
  • In order to avoid a compiler issue, the two objects are combined. This way they fit the signature of the FooAndBar type.
  • Because the types are combined, I can use the value of property in an indexer. Either the value will be fined in the combined type or undefined will be returned.
  • The return type of the sample call will be 'a' | 'b' | 'c' since the language service picks up that fieldA is a property of Foo with 'a' | 'b' | 'c' as its type.
type FooAndBar = Foo & Bar;

const getPropertyFromFooOrBar = <T extends keyof FooAndBar>(
  property: T,
  foo: Foo,
  bar: Bar
): FooAndBar[T] | undefined => {
  return { ...foo, ... bar }[property];
};

getPropertyFromFooOrBar('fieldA', { fieldA: 'a' }, { fieldY: 'hello' });
// getPropertyFromFooOrBar: <"fieldA">(property: "fieldA", foo: Foo, bar: Bar) => "a" | "b" | "c"

Upvotes: 1

Related Questions