I have this:
interface Obj {
foo: string,
bar: number,
baz: boolean
}
The desired type is this tuple:
[string, number, boolean]
How can I convert the interface to the tuple?
Update:
My original problem is: I make some opinionated library with a declarative spirit, where a user should describe parameters of a function in an object literal. Like this:
let paramsDeclaration = {
param1: {
value: REQUIRED<string>(),
shape: (v) => typeof v === 'string' && v.length < 10
},
param2: {
value: OPTIONAL<number>(),
...
},
}
Then the library takes this object and creates a function with parameters from it:
(param1: string, param2?: number) => ...
So, making such function is not a problem, the problem is to correctly type it, so that user gets good code-completion (IntelliSense).
P.S. I know it's not solvable, but it would be interesting to know what is the closest possible workaround/hack.
The structure of the tuple needs to stay the same (a string followed by a number), whereas the array can have any combination of the two types specified (this can be extended to as many types as is required).
To declare a function with a tuple return type, set the return type of the function to a tuple right after the function's parameter list, e.g. function getTuple(): [number, number] {} . If the return type of the function is not set, TypeScript will infer it as type[] .
You can declare an array of tuple also. var employee: [number, string][]; employee = [[1, "Steve"], [2, "Bill"], [3, "Jeff"]]; TypeScript generates an array in JavaScript for the tuple variable. For example, var employee: [number, string] = [1, 'Steve'] will be compiled as var employee = [1, "Steve"] in JavaScript.
To declare an array of objects in TypeScript, set the type of the variable to {}[] , e.g. const arr: { name: string; age: number }[] = [] . Once the type is set, the array can only contain objects that conform to the specified type, otherwise the type checker throws an error. Copied!
Not really an answer to the question, but since I don't actually think its possible to do, hopefully this is at least helpful in some way:
function REQUIRED<T>(): T {
//...
}
function OPTIONAL<T>(): T {
//...
}
interface ParamsDeclaration {
readonly [paramName: string]: {
readonly value: any;
readonly shape?: Function;
};
}
type Func<T> = T extends {
readonly [paramName: string]: {
readonly value: infer U;
};
} ? (...params: Array<U>) => void
: never;
function create<T extends ParamsDeclaration>(paramsDeclaration: T): Func<T> {
// ...
}
const paramsDeclaration = {
param1: {
value: REQUIRED<string>(),
shape: (v: any) => typeof v === 'string' && v.length < 10
},
param2: {
value: OPTIONAL<number>(),
//...
},
};
// Type is '(...params: (string | number)[]) => void'
const func1 = create(paramsDeclaration);
func1('1', 2); // Ok
func1(2, '1'); // Ok, but I assume not what you want
func1(Symbol()); // TS error
90% of the time you think something is impossible in Typescript, the real answer is that it is possible but you probably shouldn't do it.
Here's a solution using TuplifyUnion
from this answer, which converts a union type into a tuple type; note that we need to start from a union of the object's keys, not its values, because the values may themselves be unions (e.g. boolean
is technically true | false
).
Read that linked answer for an elaboration of what the // oh boy don't do this
comment means. If you want users of your API to specify the parameters of a function which your API generates, then the sane choice is to accept those parameter specifications in an array in the first place.
type ObjValueTuple<T, KS extends any[] = TuplifyUnion<keyof T>, R extends any[] = []> =
KS extends [infer K, ...infer KT]
? ObjValueTuple<T, KT, [...R, T[K & keyof T]]>
: R
// type Test = [string, number, boolean]
type Test = ObjValueTuple<Obj>
Playground Link
Alternate suggestions,
It needs to set orders of parameters.
interface Param {
readonly value: any;
readonly shape?: Function;
}
type Func<T extends Record<string, Param>, orders extends (keyof T)[]> = (...args:{
[key in keyof orders]:orders[key] extends keyof T ? T[orders[key]]['value']: orders[key];
})=>void;
function create<T extends Record<string, Param>, ORDERS extends (keyof T)[]>(params: T, ...orders:ORDERS): Func<T, ORDERS> {
return 0 as any;
}
const func1 = create({a:{value:0}, b:{value:''}, c:{value:true}}, 'a', 'b', 'c');
func1(0, '1', true); // ok
func1(true, 0, '1'); // error
or
ParamDeclarations with array
type Func2<T extends Param[]> = (...args:{
[key in keyof T]:T[key] extends Param ? T[key]['value'] : T[key]
})=>void;
function create2<T extends Param[], ORDERS extends (keyof T)[]>(...params: T): Func2<T> {
return 0 as any;
}
const func2 = create2({value:0}, {value:''}, {value:true});
func2(0, '1', true); // ok
func2(true, 0, '1'); // error
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With