typescript create default value for nested optional object type

Viewed 540

I have a type MyType

type MyType = {
  a?: {c: string},
  b?: {e: string},
}

that contains optional nested objects. I want to create a function getDefaultValue that accepts a key K of MyType and returns a default representation of the nested object that is specified by key K.

function getDefaultValue<K extends keyof MyType>(key: K):Required<MyType>[K] {
  if (key === 'a') return {c: 'hello'}
  return {e: 'hello'}
}

const variable: Required<MyType>['a'] = getDefaultValue('a')

I get the following typescript errors:

TS2322: Type '{ c: string; }' is not assignable to type 'Required<MyType>[K]'.
  Type '{ c: string; }' is not assignable to type '{ c: string; } & { e: string; }'.
    Property 'e' is missing in type '{ c: string; }' but required in type '{ e: string; }'.
TS2322: Type '{ e: string; }' is not assignable to type 'Required<MyType>[K]'.
  Type '{ e: string; }' is not assignable to type '{ c: string; } & { e: string; }'.
    Property 'c' is missing in type '{ e: string; }' but required in type '{ c: string; }'.
2 Answers

Matching the types of returned values with generic parameters can be a bit tricky, but in this case you can avoid the problem altogether by putting the defaults in an object

const defaults = {
  a: { c: 'hello' },
  b: { e: 'hello' },
}

and declaring getDefaultValue like this:

function getDefaultValue<K extends keyof MyType>(key: K) {
  return defaults[key]
}

const test1 = getDefaultValue('a') // inferred type: {c: string}
const test2 = getDefaultValue('b') // inferred type: {e: string}

Missing default values will be signalled by a type error at defaults[key].

TypeScript playground

I think it is impossible to do it with TypeScript as you are trying to use runtime values instead of types to create a generic.

Please consider the following example and try to answer the questions:

const key: 'a' | 'b' = 'a';
const variable: Required<MyType>['a'] = getDefaultValue(key)
  1. How to guarantee that the result type is basically Required<MyType>['a'] not Required<MyType>['b']
  2. How typescript should understand that K extends keyof MyType is a type of one value?

P.S. As a hack you can specify any as a return type, but it is not something that you need to do with TypeScript

type MyType = {
  a?: {c: string},
  b?: {e: string},
}

type MyKeys = keyof MyType;

function getDefaultValue(key: MyKeys): any {
  if (key === 'a') {
    return {c: 'hello'};
  }
  return {e: 'hello'}
}

const variable: Required<MyType>['a'] = getDefaultValue('a')

P.P.S. I would recommend creating a function that will return a default value for the whole object and simply grab what you need

function getDefaultValue(): Required<MyType> {
  return {
    a: {c: 'hello'},
    b: {e: 'hello'},
  };
}
const variable = getDefaultValue().a;
Related