简体   繁体   中英

Infer conditional return generic types by object property

I am trying to infer the generic type of Model per property. Everything currently is showing as unknown[] instead of the desired types in the comments below.

playground

 class Model<T> { x?: T } type ArgumentType<T> = T extends Model<infer TInner>? Model<TInner>: ( T extends Model<infer TInner>[]? Model<TInner>[]: never ); type ReturnType<T> = T extends Model<infer TInner>? TInner[]: ( T extends Model<infer TInner>[]? TInner[][]: never ); const test = <TInner, TValue extends ArgumentType<TInner>, T extends Record<string, TValue>>(models: T): Record<keyof T, ReturnType<TValue>> => { return; } const numberModel = new Model<number>(); const stringModel = new Model<string>(); const bigintModel = new Model<bigint>(); const result = test({ prop1: [numberModel, numberModel, numberModel], prop2: [stringModel], prop3: stringModel, prop4: [numberModel, numberModel, numberModel, bigintModel], prop5: stringModel }); //expected types result.prop1 // number[][] result.prop2 // string[][] result.prop3 // string[] result.prop4 // (number | bigint)[][] result.prop5 // string[] //actual types result.prop1 // unknown[] result.prop2 // unknown[] result.prop3 // unknown[] result.prop4 // unknown[] result.prop5 // unknown[]

My suggestion for your test() function would be to give it the following call signature:

declare const test: <T extends Record<keyof T, Model<any> | Model<any>[]>>(
  models: T
) => { [K in keyof T]:
    T[K] extends Model<infer U> ? U[] :
    T[K] extends Model<infer U>[] ? U[][] :
    never
  }

Note here that we have just a single generic type parameter T , correponding exactly to the type passed in for the models function parameter. This T is constrained to Record<keyof T, Model<any> | Model<any>[]> Record<keyof T, Model<any> | Model<any>[]> , meaning that it can have any keys whatsoever, but its property values must all either be a Model<X> for some X , or an array of Model<X> for some X . This is a fairly direct and specific constraint.

In your original version you had three type parameters, each of which was constrained to some type function of another, but TypeScript's compiler is unable to do inference that way. If you have type parameters U, V extends F<U>, W extends F<V> you might get inference if you give the compiler a value of type U , but it is essentially impossible to do it if you only give the compiler a value of type W . It can't run the engine backwards to make W do something to V and then make V do something to U . A single type parameter is a lot easier to work with.

Then note that the output type

{ [K in keyof T]:
    T[K] extends Model<infer U> ? U[] :
    T[K] extends Model<infer U>[] ? U[][] :
    never
}

is essentially your ( unfortunately named ) ReturnType<> type alias but mapped over the properties of T . For each property key K in the type T corresponding to models , we take the property value type T[K] and do something like your ReturnType<T[K]> .


Let's test it out:

const numberModel = new Model<number>();
const stringModel = new Model<string>();
const bigintModel = new Model<bigint>();

const result = test({
  prop1: [numberModel, numberModel, numberModel],
  prop2: [stringModel],
  prop3: stringModel,
  prop4: [numberModel, numberModel, numberModel, bigintModel],
  prop5: stringModel
});

/* const result: {
    prop1: number[][];
    prop2: string[][];
    prop3: string[];
    prop4: (number | bigint)[][];
    prop5: string[];
} */

Looks good!

Playground link to code

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM