Pass generic type argument through the literal object with different field names per each field if number of fields values is limited by 4

断了今生、忘了曾经 提交于 2020-08-10 18:15:07

问题


What am I doing?

I want to make mapping of an object field-by-field to objects of some generic type. I have 4 runtime cases (with runtime check and throwing exception if not matches) that I've used as a properties of original object:

  • false transforms to Smth<unknown> | undefined
  • true transforms to Smth<unknown>
  • [] transforms to Smth<unknown> with 0 or more elements
  • [true] transforms to Smth<unknown> with 1 or more elements

Actually that's not very difficult, but I'm not very happy to have unknown in generic everywhere. In some cases I want to specify what payload exactly this Smth should have (compile-time assertion only, no any runtime checks planned). For this purpose I've found solution with casting the four values to types having additional optional field:

type WithOptPayload<T, P> =  T & { payload?: P }
getColDefsByKinds({ b: false as WithOptPayload<false, { u: 99 }> })

And that really works fine (playground):

type WithOptPayload<T, P> =  T & { payload?: P }

const x = getColDefsByKinds({
    a: false,
    b: false as WithOptPayload<false, { u: 99 }>,
    c: true,
    d: true as WithOptPayload<true, { v: string }>,
    e: [],
    f: [] as WithOptPayload<[], { w: number }>,
    g: [true],
    h: [true] as WithOptPayload<[true], any>,
});

type IfExtends<L, R, Y, N = never> = L extends R ? Y : N

interface Smth<P = unknown> {
    x: string
    payload: P
}

type Kind = false | true | readonly [] | readonly [true]
type Unpack<F extends Kind, Res> = IfExtends<F, true, Res, IfExtends<F, false, Res | undefined, Res[]>>

function getColDefsByKinds<T extends { [key: string]: Kind }>(how: T): { [kind in keyof T]: Unpack<T[kind], Smth<T[kind] extends { payload?: infer P } ? P : unknown>> } {
    return null!
}

What's the problem?

As you see, in the line

b: false as WithOptPayload<false, { u: 99 }> }

I need to use type false twice. Not a long word to write, but there is the other issue: typescript considers true as false is fine. So following lines are compiled without errors and lead to mismatch of runtime and compiletime types:

x: false as WithOptPayload<true, { u: 99 }>,
y: true as WithOptPayload<false, { u: 99 }>,
z: [true] as WithOptPayload<[], any>,

I need such casts to be an error.

Or maybe I can use some other way of passing a type to the mapping function. But there is a limitation: all property names should be written only once as they are different from call to call and do not match any interface.

来源:https://stackoverflow.com/questions/62842858/pass-generic-type-argument-through-the-literal-object-with-different-field-names

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!