Home > Blockchain >  Conditional object types when value changes
Conditional object types when value changes

Time:01-11

Sorry about the question title, I am not quite sure how to phrase it.

I am fairly new to Typescript and wanted to add strong types to a new project.

Lets say I have a function that accepts two parameters: value and params written like

type Value = 'category' | 'follows' | 'users'; 

request(value: Value, params: object)

As you can see Value type only accepts three predefined values. But params should be conditionnaly typed when value changes:

If value is category, then params should type as { author: string, year: number }, if it is users then it should type { id: number } OR { name: string }

And that's the whole challenge of it. I can do it with objects and indexes but have been stuck with types.

Is it even possible to do this ?

Thanks a lot !

NVH

CodePudding user response:

There's a lot of ways to do this, but here are two.


Function overloads (docs)

function request(value: 'category', params: { author: string, year: number }): void
function request(value: 'users', params: { id: number } | { name: string }): void
function request(
    value: 'category' | 'users',
    params: { author: string, year: number } | { id: number } | { name: string }
) {
    // TODO: implementation
}

Playground


Lookup type with a generic function (docs)

type Value = 'category' | 'follows' | 'users'; 
type ObjectLookup = {
    category: { author: string, year: number }
    users: { id: number } | { name: string }
    follows: { foo: string }
}

function request<T extends Value>(value: T, params: ObjectLookup[T]) {
  // TODO: implementation...
}

Playground

CodePudding user response:

It works only with TS nightly, see this PR.

type Value = 'category' | 'follows' | 'users';


type Arguments =
    | ['category', { author: string, year: number },]
    | ['users', { id: number } | { name: string }]
    | ['follows', Record<PropertyKey, unknown>]

const request = (...[value, params]: Arguments) => {
    if (value === 'category') {
        params.author // ok
        params.year
    }


    return 'UNIMPLEMENTED'
}

Playground

See rest-parameters-with-tuple-types for more explanation about using spread syntax in ts

However, you might have a problems with users. See example:

const request = (...[value, params]: Arguments) => {
    if (value === 'category') {
        params.author // ok
        params.year
    }

    if(value==='users'){
        params.id // <------------- error
    }

    return 'UNIMPLEMENTED'
}

TypeScript does not allow you to get params.id. In fact, TS does not allow you to get any property of params if value is users because union { id: number } | { name: string } does not have common properties. Hence, the safest way is to forbid using any property.

In order to narrow params type when value is users I think it worth using custom type guard:

type Value = 'category' | 'follows' | 'users';

type UsersId = { id: number }
type UsersName = { name: string }

// TYPE GUARD
const isUserId = (data: UsersId | UsersName): data is UsersId => 'id' in data

// TYPE GUARD
const isUserName = (data: UsersId | UsersName): data is UsersName => 'name' in data

type Arguments =
    | ['category', { author: string, year: number },]
    | ['users', UsersId | UsersName]
    | ['follows', Record<PropertyKey, unknown>]

const request = (...[value, params]: Arguments) => {
    if (value === 'category') {
        params.author // ok
        params.year
    }

    if (value === 'users') {
        if(isUserId(params)){
            params.id // ok
        }
    }

    return 'UNIMPLEMENTED'
}

CodePudding user response:

Function Overloading

Example:

type Value = 'category' | 'follows' | 'users'; 
function request(value: 'category', params: Type1):ResponseType;
function request(value: 'follows', params: Type2):ResponseType;
function request(value: 'users', params: Type3):ResponseType;
function request(value: Value, params: Object):ResponseType{
    ...implementation
}

TS is able to see what the type of a prop is and infer what the other props in the respective variant of an overloaded function are.

  •  Tags:  
  • Related