问题
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 toSmth<unknown> | undefined
true
transforms toSmth<unknown>
[]
transforms toSmth<unknown>
with 0 or more elements[true]
transforms toSmth<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