I have a function that uses generics. The generics in question is just <T extends anInterfaceName>. The problem is, in the function I need to get the keys of the interface used in T. After doing some research, i found this module.
Problem is, it doesn't work with generics. I can't seem to find anything that can help, is there anything that could do this? Is there a work around to this problem?
Here you have several wayt to get keys:
interface Keys {
name: string;
age: number;
}
const fun1 = <T,>(obj: T, key: keyof T): T[keyof T] => null as any
const fun2 = <T, K extends keyof T>(obj: T, key: K): T[K] => null as any
const fun3 = (key: keyof Keys) => null
const fun4 = <T extends Keys, K extends keyof T>(obj: T, key: K) => null
If above code does not work for you, please provide an example what are you expect.
UPDATE
/**
* UPDATE
*/
const fun5 = <T,>(): Array<keyof T> => null as any
const fun6 = <T,>(): (keyof T)[] => null as any
const result = fun5<Keys>() // ("name" | "age")[]
UPDATE 2
If you want more strict type of array, you can use next util:
//Credits goes to https://stackoverflow.com/questions/50374908/transform-union-type-to-intersection-type/50375286#50375286
type UnionToIntersection<U> =
(U extends any ? (k: U) => void : never) extends ((k: infer I) => void) ? I : never
// Credits goes to ShanonJackson https://github.com/microsoft/TypeScript/issues/13298#issuecomment-468114901
type UnionToOvlds<U> = UnionToIntersection<U extends any ? (f: U) => void : never>;
// Credits goes to ShanonJackson https://github.com/microsoft/TypeScript/issues/13298#issuecomment-468114901
type PopUnion<U> = UnionToOvlds<U> extends ((a: infer A) => void) ? A : never;
// Credit goes to Titian Cernicova-Dragomir https://stackoverflow.com/questions/53953814/typescript-check-if-a-type-is-a-union#comment-94748994
type IsUnion<T> = [T] extends [UnionToIntersection<T>] ? false : true
// Finally me)
type UnionToArray<T, A extends unknown[] = []> = IsUnion<T> extends true ? UnionToArray<Exclude<T, PopUnion<T>>, [PopUnion<T>, ...A]> : [T, ...A]
interface Person {
name: string;
age: number;
surname: string;
children: number;
}
type Result = UnionToArray<keyof Person>
const func = <T,>(): UnionToArray<keyof T> => null as any
const result = func<Person>() // ["name", "age", "surname", "children"]
I really don't know other wayt to get keyof's.
Please keep in mind, this solution is not CPU friendly and there is no order guarantee.
UPDATE 3
Here is much safer option to obtain keys. Next solution takes into account that order could not be preserved:
type TupleUnion<U extends string, R extends any[] = []> = {
[S in U]: Exclude<U, S> extends never ? [...R, S] : TupleUnion<Exclude<U, S>, [...R, S]>;
}[U];
interface Person {
firstName: string;
lastName: string;
dob: Date;
hasCats: false;
}
type keys = TupleUnion<keyof Person>;//
Shamelessly stolen from Wroclaw twitter TS group
I think you are using Typescript in the wrong way.
If you are really interested in going that path you probably can use Typeguards.
https://www.typescriptlang.org/docs/handbook/advanced-types.html
but the problem here is that anInterfaceName needs to have the keys of what you are interested to grab, T extends anInterfaceName means I can be anything that has the anInterfaceName keys, so there's an infinite possibilities of T classes..
Related
The problem itself is more complex than this but I'll try to explain in a simple way.
I have an object (it could be a different object, lets say a car o user or whatever).
const car = {
model: 'Model A',
specs: {
motor: {
turbo: true
}
}
};
Then I have a function to get a value from a property name.
interface Data<T, K extends keyof T> {
keynames: string[];
values: Array<T[K] | null>;
}
const getValues = <T, K extends keyof T>(keynames: string[], data: T): Data<T, K> => {
const values: Array<T[K] | null> = [];
keynames.forEach(keyname => {
const value: T[K] | undefined = getObjectValue(data, keyname);
if (typeof value === 'undefined') {
values.push(null);
} else {
values.push(value);
}
});
return {
keynames,
values
}
};
// This works with key.name syntax, specs.motor.turbo returns true for example
const getObjectValue = <T, K extends keyof T>(
object: T,
keyName: string
): T[K] | undefined => {
const keys = keyName.split('.');
if (keys.length === 1) {
if (typeof object === 'object') {
if (keys[0] in (object as T)) {
return (object as T)[keys[0] as K];
}
return undefined;
}
return undefined;
} else {
const [parentKey, ...restElements] = keys;
if (!object) return undefined;
return (getObjectValue(
(object as T)[parentKey as K],
restElements.join('.')
) as unknown) as T[K];
}
};
The problem becomes when I write some tests for example:
// ...
const data = getValues(['model', 'specs.motor.turbo'], car);
assert.deepEqual(data, {
keynames: ['model', 'specs.motor.turbo'],
values: ['Model A', true],
});
// ...
data is what I'm expecting, my function returns the same object as expected but I'm getting an error:
Type 'boolean' is not assignable to type 'string | { motor: { turbo: boolean; }; }'.ts(2322)
which is obvious since I'm only inferring T[K] in my function and not the type of the nested properties.
How to achieve that, so my function works with return types string | { motor: { turbo: boolean; }; } | { turbo: boolean; } | boolean. Or maybe there is a simpler way to return a nested property value.
I'm going to preserve your implementation as much as possible, even though there might be some improvements there that someone would suggest. I'm primarily taking this question as "how can I tell the TypeScript compiler what getValues() is doing?".
First, we need to represent what comes out when you deeply index into a type T with a dotted keypath K. This is only going to be possible in TypeScript 4.1 and later, since the following implementation relies both on template literal types as implemented in microsoft/TypeScript#40336, and recursive conditional types as implemented in microsoft/TypeScript#40002:
type DeepIndex<T, K extends string> = T extends object ? (
string extends K ? never :
K extends keyof T ? T[K] :
K extends `${infer F}.${infer R}` ? (F extends keyof T ?
DeepIndex<T[F], R> : never
) : never
) : never
The general plan here is to check if K is a key of T; if so, we do the lookup with T[K]. Otherwise we split K at the first dot into F and R, and recurse downward, indexing into T[F] with the key R. If at any point something goes wrong (F is not a valid key of T, for example), this returns never instead of property type. We will use this; if something becomes never then we assume that there was a bad index:
type ValidatePath<T, K> =
K extends string ? DeepIndex<T, K> extends never ? never : K : never;
The type ValidatePath<T, K> will extract from the (possible union of keys) K just those members which are valid paths.
Before we get into it, let's also represent what you do when you replace undefined with null:
type UndefinedToNull<T> = T extends undefined ? null : T;
For your code, I'm going to go ahead and use tuple types to represent the keynames and values arrays in Data. This should fall back to unordered arrays if necessary, but it seems weird to throw away information you know; for example, on your car example, you know that the first element of the values array is a string and the second is a boolean. A type like [string, boolean] has more information than Array<string | boolean | null>:
interface Data<T, K extends string[]> {
keynames: K;
values: { [I in keyof K]: UndefinedToNull<DeepIndex<T, Extract<K[I], string>>> };
}
The values property uses a mapped array/tuple to convert the tuple of keynames in K to a tuple of deep indexed values.
Now we need to give strong types to your function signatures. The compiler won't be able to verify a lot of the type safety inside of the implementations, so I'll be doing a lot of type assertions with as to force the compiler to accept what we're doing:
const getValues = <T, K extends string[]>(keynames: (K & { [I in keyof K]: ValidatePath<T, K[I]> }) | [], data: T): Data<T, K> => {
const values = [] as { [I in keyof K]: UndefinedToNull<DeepIndex<T, Extract<K[I], string>>> };
const _keynames = keynames as K;
_keynames.forEach(<I extends number>(keyname: K[I]) => {
const value: DeepIndex<T, Extract<K[I], string>> | undefined = getObjectValue(data, keyname as any);
if (typeof value === 'undefined') {
values.push(null as UndefinedToNull<DeepIndex<T, K[I]>>);
} else {
values.push(value as UndefinedToNull<DeepIndex<T, K[I]>>);
}
});
return {
keynames: _keynames,
values
}
};
const getObjectValue = <T, K extends string>(
object: T,
keyName: K & ValidatePath<T, K>
): DeepIndex<T, K> | undefined => {
const keys = keyName.split('.');
if (keys.length === 1) {
if (typeof object === 'object') {
if (keys[0] in (object as T)) {
return (object as T)[keys[0] as keyof T] as DeepIndex<T, K>;
}
return undefined;
}
return undefined;
} else {
const [parentKey, ...restElements] = keys;
if (!object) return undefined;
return (getObjectValue(
(object as T)[parentKey as keyof T],
restElements.join('.') as any as never
) as unknown) as DeepIndex<T, K>;
}
};
I wouldn't worry too much about the assertions inside the implementation. The important piece here is the call signature to getValues():
<T, K extends string[]>(keynames: (K & { [I in keyof K]: ValidatePath<T, K[I]> }) | [], data: T) => Data<T, K>
We are interpreting the keynames as something assignable to an array of string values. The | [] at the end is just a hint that the compiler should prefer viewing, say, ['model', 'specs.motor.turbo'] as an ordered pair instead of an unordered array. The intersection with {[I in keyof K]: ValidatePath<T, K[I]>}, a mapped tuple, should be a no-op is all the elements of K are valid paths into T. If any element of K is an invalid path, though, that element of the mapped tuple will be never and the validation will fail.
Let's test it:
const car = {
model: 'Model A',
specs: {
motor: {
turbo: true
}
}
}
const data = getValues(['model', 'specs.motor.turbo'], car);
data.keynames; // ["model", "specs.motor.turbo"]
data.values; // [string, boolean]
console.log(data);
This looks like exactly what you want with your example. What happens if we misspell a key?
getValues(['model', 'specs.motar.turbo'], car); // error!
// ---------------> ~~~~~~~~~~~~~~~~~~~
// Type 'string' is not assignable to type 'undefined' 🤷♂️
We get an error on the offending key. The error message isn't that useful, unfortunately. When I tried to use the answer to this question to give the compiler a full list of exactly which dotted paths to accept, it caused a massive slowdown and even some "type instantiation is excessively deep or infinite" errors. So while it would be nice to see "specs.motar.turbo" is not assignable to "model" | "specs" | "specs.motor" | "specs.motor.turbo"`, sadly I can't get that to happen in a reasonable way.
What if there could be an undefined value at the property in question?
const d2 = getValues(['a.b'], { a: Math.random() < 0.5 ? {} : { b: "hello" } });
d2.keynames; // ["a.b"]
d2.values; // [string | null]
console.log(d2);
It comes out as | null instead of | undefined, which is good.
So that works as well as I could get it to. There are undoubtedly limitations and edge cases. Deep indexing with dotted keys is kind of near the edge of what works in TypeScript (before 4.1 is was significantly past the edge, so that's something, right?). For example, I'd expect weird/bad things to happen with optional or union-typed properties at non-leaf nodes of the object tree. Or objects with string index signatures, for that matter. These might be addressable, but it would take some effort and a lot of testing. The point is: tread carefully.
Playground link to code
I would like for a higher order function to be able to catch the signature parameters of the passed function which can have different signature.
I don't know if it's feasible but this was my approach to it :
type FuncA = (a: string, b: number) => void
type FuncB = (a: string) => void
type Func = FuncA | FuncB
const a: FuncA = (a: string, b: number) => {
console.log('FuncA')
}
const b: FuncB = (a: string) => {
console.log('FuncB')
}
// My higher order function
const c = (func: Func) => {
// do something here...
return (...args: Parameters<typeof func>) => {
func(...args) // Expected 2 arguments, but got 0 or more. ts(2556). An argument for 'a' was not provided.
}
}
My higher order function c couldn't pass the parameters of func
It seems like TypeScript cannot discriminate the different possible signature of type Func.
Does anyone know a pattern to write this kind of code?
Thank you !
This is a tough one because for a function to extend another function doesn't mean quite what you think.
We want the function created by c to require that arguments correspond to the function that it was given. So we use a generic to describe the function.
const c = <F extends Func>(func: F) => {
return (...args: Parameters<F>) => {
func(...args); // still has error
}
}
At this point we still have that error, but when we call c, we get a function which has the right arguments based on whether we gave it a or b.
const cA = c(a); // type: (a: string, b: number) => void
cA("", 0);
const cB = c(b); // type: (a: string) => void
cB("");
As for the error, it has to do with what it means for a function to extend another function. Try changing F extends Func to F extends FuncA and F extends FuncB to see what happens. With F extends FuncB we get an error on c(a), but with F extends FuncA we don't get an error on c(b). Huh?
If you think about it in terms of a callback it makes sense. It's ok to pass a function that requires less arguments than expected, but not ok to pass one that requires more. But we are the ones implementing the callback so this creates a problem for us. If we extend type Func with a function that has no arguments, the empty array from Parameters<F> isn't sufficient to call either type.
We have to make our generic depend on the arguments instead.
type AP = Parameters<FuncA> // type: [a: string, b: number]
type BP = Parameters<FuncB> // type: [a: string]
type Args = AP | BP;
const c = <A extends Args>(func: (...args: A) => void) => {
return (...args: A) => {
func(...args) // no error
}
}
Typescript Playground Link
If you're ok with the decorated function being any function, you could do:
const c = <T extends (...a: any) => any>(func: T) => {
// do something here...
return (...args: Parameters<typeof func>): ReturnType<T> => {
return func(...args);
}
}
Calling it would look like
c<typeof a>(a)('a', 2)
I'm having trouble coming up with the necessary typings for the following two functions:
const pick = (obj, ...names) =>
names.reduce((ret, next) => {
if (typeof obj[next] != 'undefined') ret[next] = obj[next];
return ret;
}, {});
const strip = (obj, ...names) =>
pick(obj, ...Object.keys(obj).filter(key => !names.includes(key)));
I've tried something like the following:
type Picker = <T extends object, U extends (keyof T)[]>(obj: T, ...names: U) =>
Pick<U, T>;
type Stripper = <T extends object, U extends (keyof T)[]>(obj: T, ...names: U) =>
Omit<U, T>;
But those types require passing a type T that extends object, not array of strings. How can I construct a type from Array<keyof T> to pass into Pick and Omit?
After rephrasing the question a couple of times, it occured to me that you could create a union type from an array by using the index accessor:
type Picker = <U extends object, V extends (keyof U)[]>(obj: U, ...keys: V) =>
Pick<U, V[number]>;
type Stripper = <U extends object, V extends (keyof U)[]>(obj: U, ...keys: V) =>
Omit<U, V[number]>;
What I want to do
I'm currently looping over an object's keys and transferring the values to another object.
interface From {
[key: string]: string;
}
let from: From = {
prop1: "foo",
prop2: "23",
};
interface To {
[key: string]: string | number | boolean | undefined;
prop1: string;
prop2: number;
prop3: boolean;
}
let to: To = {} as To;
const initEnv = async () => {
const keys: string[] = Object.keys(from);
for (let i = 0; i < keys.length; i++) {
let key: string = keys[i];
let keyType = typeof (key as keyof To); // This only returns "string". Which kind of makes sense to me
let keyType = typeof keyof key; // SyntaxError: ',' expected
let keyType: typeof to[key]; // Error: 'key' refers to a value, but is being used as a type
to[key] = from[key];
}
};
I would want to be able to, say switch the value, so I don't want to just extract the type of the key. I want to assign it to a variable for use in the code, thus at runtime, as a string for instance.
So I think things like this wouldn't work.
let keyType2: typeof env[key]; // Error: 'key' refers to a value, but is being used as a type
Maybe, but the question is then; what do I assign to this variable?
The reason for all this, is that I want to convert the from variables to the correct type before, assigning them to the to object.
So yeah, basically, my question is how I would extract the type (as a string, at runtime, dynamically) from the key. Or is it even possible in the first place? And if it isn't why not? I like understanding things.
Thanks for putting up with my bad english, as well.
There are no interfaces at runtime; TypeScript's type system is erased from the emitted JavaScript. Your from and to values will be evaluated like this at runtime:
let from = {
prop1: "foo",
prop2: "23",
};
let to = {};
There's no From or To, and no way to use To to figure out how to coerce from's properties into the right types. The type system has no runtime effects.
The usefulness of TypeScript's type system comes from describing what will happen at runtime and not from affecting things at runtime. Imagine how you would have to write your code in pure JavaScript, and then give types to that code. Here's one way I might do it. Instead of a To interface, let's make a To object whose properties are functions that coerce inputs to other types:
const To = {
prop1: String,
prop2: Number,
prop3: Boolean
}
This is enough information to proceed at runtime.
Now, if you were going to build to manually, the compiler would be able to understand that the resulting value has a prop1 property of type string and a prop2 property of type number:
const toManual = { prop1: To.prop1(from.prop1), prop2: To.prop2(from.prop2) };
/* const toManual: { prop1: string; prop2: number; } */
But you don't want to do it manually; you'd like to write a loop that walks through the keys of from and uses To to produce properties of to. This is harder for the compiler to understand, but with judicious use of type assertions and type annotations you can write an objMap function that works programmatically:
function objMap<T, F extends { [K in keyof T]: (arg: T[K]) => any }>(
obj: T, fMap: F) {
const ret = {} as { [K in keyof T]: ReturnType<F[K]> };
const fM: { [K in keyof T]: (arg: T[K]) => ReturnType<F[K]> } = fMap;
(Object.keys(obj) as Array<keyof T>).forEach(<K extends keyof T>(k: K) => {
ret[k] = fM[k](obj[k]);
})
return ret;
}
The objMap function takes an object obj and a mapping object fMap which has at least all the same keys as obj and whose properties are functions that map obj's properties. The return type of objMap is an object whose properties are all the returned values of the fMap function for each property in obj. The actual work is being done by ret[k] = fM[k](obj[k]);. It's the programmatic equivalent of ret.prop1 = To.prop1(from.prop1); and ret.prop2 = To.prop2(from.prop2).
Let's see if it works:
const to = objMap(from, To);
/* const to: { prop1: string; prop2: number; } */
console.log(JSON.stringify(to));
// {"prop1":"foo","prop2":23}
That looks correct; the type of to is inferred by the compiler to be {prop1: string, prop2: number}, and the actual value of to is computed at runtime to be {prop1: "foo", prop2: 23}.
Okay, hope that helps; good luck!
Playground link to code
Problem
Is there a way in Typescript to define a type that is only a string literal, excluding string itself?
Note that I am not talking about a certain list of string literal; for which, a simple union of "Value1" | "Value2", or an enum type would work. I am talking about any string literal, but not string itself.
Example Code
type OnlyStringLiterals = ...; // <--- what should we put here?
const v1: OnlyStringLiterals = "hi"; // should work
const v2: OnlyStringLiterals = "bye"; // should work
// and so should be for any single string value assigned
// But:
const v3: OnlyStringLiterals = ("red" as string); // should NOT work -- it's string
Use Case
I am doing Branding on the types in my code, and I am passing a brand name, as a template, to my parent class. See the code below:
abstract class MyAbstractClass<
BRAND_T extends string,
VALUE_T = string
> {
constructor(private readonly _value: VALUE_T) { }
getValue(): VALUE_T { return this._value; }
private _Brand?: BRAND_T; // required to error on the last line, as intended!
}
class FirstName extends MyAbstractClass<"FirstName"> {
}
class AdminRole extends MyAbstractClass<"AdminRole"> {
}
class SubClassWithMissedName extends MyAbstractClass<string> {
// I want this to error! ........................ ^^^^^^
}
function printName(name: FirstName) {
console.log(name.getValue());
}
const userFirstName = new FirstName("Alex");
const userRole = new AdminRole("Moderator");
printName(userRole); // Already errors, as expected
Playground Link
I want to make sure every subclass is passing exactly a string literal, and not just string to the parent class.
I found an answer that works for my use case, but is not the most reusable one. Just sharing it anyway.
Thought Process
I believe it's not possible to have one solid type to represent what I wanted, because I cannot even think what will show up in VS Code if I hover over it!
However, to my knowledge, there is a function-style checking in Typescript for types that you can pass a type in and expect a type back, and finally assign a value to it to see if it goes through.
Type-checking using a Generic Type and a follow-up assignment
Using this technique I am thinking about the following template type:
type TrueStringLiterals<T extends string> = string extends T ? never : true;
const v1 = "hi";
const check1: TrueStringLiterals<typeof v1> = true; // No error :-)
const v2 = "bye";
const check2: TrueStringLiterals<typeof v2> = true; // No error :-)
const v3 = ("red" as string);
const check3: TrueStringLiterals<typeof v3> = true; // Errors, as expected!
Playground Link
Easier in an already-passed Generic Type
Also, in my use case, I am doing:
abstract class MyAbstractClass<
BRAND_T extends (string extends BRAND_T ? never : string),
VALUE_T = string
> {
...
Playground Link
... which works like a charm!
You can create utility type which will allow only on subset of string:
type SubString<T> = T extends string ?
string extends T ? never
: T
: never
const makeSubStr = <T extends string>(a: SubString<T>) => a
const a = makeSubStr('strLiteral')
const b = makeSubStr('strLiteral' as string) // error
const c: string = 'elo I am string'
const d = makeSubStr(c) // error
const e: SubString<"red"> = ("red" as string); // error
This type will also return never if something is not a string, in your answer TrueStringLiterals will not take this case into consideration and pass it through.
The other answers don't catch the case where the provided type parameter is a union of literal strings. If this shall be explicitly avoided, as could be read from the OPs question, the following solution, based on the other two can be used:
type UnUnion<T, S> = T extends S ? ([S] extends [T] ? T : never) : never;
type NotUnion<T> = UnUnion<T, T>;
type LiteralString<T extends string> = string extends T ? never : NotUnion<T>;
where UnUnion uses the fact that if T is a union, say 'a' | 'b', the union is distributed over the rest of the type expression.
(['a'|'b'] extends ['a'] ? ... ) | (['a'|'b'] extends ['b'] ? ...)
If T is a union, none of these can hold and all the parts turn into never.
NotUnion reduces this to have just one generic parameter and LiteralString just uses its result in case its parameter is not extendable by string.
Playground Link
I'd like to submit an answer from a similar question I recently asked, that is far more simple than the examples given so far:
type SpecificString<S extends Exclude<string, S>> = S
let test1: SpecificString<"a" | "b" | "c"> // okay
let test2: SpecificString<string> // error
//guaranteed to work where `Exclude<string, T>` wouldn't
let test3: Exclude<SpecificString<"a" | "1">, "1">
test3 = "a" // okay
test3 = "1" // error
Basically how this works:
Exclude<string, "any string literal"> ==> resolves to string
Exclude<string, string> ==> resolves to never
You can call this F-bounded quantification if you like I guess.