TaeSang Cho
TaeSang Cho

Reputation: 245

Typescript infer type by sibling property

I'm trying to make a framework with typescript.
and I need a compile time data check.

My typescript interface code

type TypeInfo = 'string' | 'number' | 'boolean';

type GetType<T extends TypeInfo> = T extends 'string'
  ? string
  : T extends 'number'
  ? number
  : T extends 'boolean'
  ? boolean
  : never;

interface StateInfo {
  // TypeInfo
  type: TypeInfo;
  default: GetType<this['type']>;
}

Expected (compile time)

// OK
const test1: StateInfo = {
  type: 'number',
  default: 1,
};

// Bad ('default' property is not number or 'type' property is not 'string') 
const test2: StateInfo = {
  type: 'number',
  default: 'test',
};

But, the typescript compile the code without any errors.
How can I declare type of 'default' property correctly?

Upvotes: 1

Views: 1729

Answers (2)

@Shivam Singla 's answer is perfectly valid and I believe should be accepted. Please treat my answer as add-on )

If you want to infer smth, the best way, I believe, to use function with generics:

type TypeInfo = 'string' | 'number' | 'boolean';

type GetType<T extends TypeInfo> = T extends 'string'
    ? string
    : T extends 'number'
    ? number
    : T extends 'boolean'
    ? boolean
    : never;

interface StateInfo {
    // TypeInfo
    type: TypeInfo;
    default: GetType<this['type']>;
}

const test = <T extends TypeInfo>(arg: T, prop: GetType<T>): StateInfo => {
// this return valu is just a mock, you should provide here your valid code
    return null as any
}

const result = test('string','str') // ok
const result2 = test('number',1) // ok
const result3 = test('boolean', true) // ok

const result4 = test('boolean', 1) // expected error
const result5 = test('string', false) // expected error

Now, your StateInfo is doing what you want.

Upvotes: 2

Shivam Singla
Shivam Singla

Reputation: 2201

Problem

GetType<this['type']> is evaluated during declaration of StateInfo. What I mean is that, the type of default will be string | number | boolean and will not be affected by the value of property type while creating objects.

Solution

Use Discriminated Unions

type StateInfoString = {
  type: 'string'
  default: string
}

type StateInfoNumber = {
  type: 'number'
  default: number
}

type StateInfoBoolean = {
  type: 'boolean'
  default: boolean
}

type StateInfo = StateInfoString | StateInfoNumber | StateInfoBoolean

// OK
const test1: StateInfo = {
  type: 'number',
  default: 1,
};

// error
const test2: StateInfo = {
  type: 'number',
  default: 'test',
};

Playground

Upvotes: 4

Related Questions