Fixed length array in TypeScript with reference to the length - arrays

Is it possible to define a type foo following this pseudo-code?
type foo<T> = {
length: number;
array: T[length]
}
Alternatively:
type foo<T> = {
length: array.length;
array: T[]
}
For example, {length: 2, array: ["a","b"]} should have this type, but {length: 3, array: ["a","b"]} should not.
It has been answered before how to define a FixedLengthArray<T,N extends number> type. We can use this to define:
type foo<T,N extends number> = {
length: N;
array: FixedLengthArray<T,N>
}
This works when I want to define
let bar : foo<string,2> = {length: 2, array: ["a","b"]};
But my goal is to write
let bar : foo<string> = {length: 2, array: ["a","b"]};
Can we somehow let TypeScript infer the length?

TypeScript won't infer any generic parameter elsewhere than in a function call parameters. So you could use a function and variadic tuples in order to achieve that:
type Foo<U extends Array<any>, T extends [...U]> = {
length: T['length'];
array: T;
}
declare function handleFoo<U extends Array<any>, T extends [...U]>(param: Foo<U, T>): any;
handleFoo({ length: 3, array: ["a", "b"] }); // Error
handleFoo({ length: 2, array: ["a", "b"] }); // OK
TypeScript playground

Related

how to check is interface's two props length is same?

I want to be two array props's length must be same.
but I do not know how to write it.
as is my code is ...
// useFoo.ts
interface Props {
initialMessage: string;
messageList: string[];
messageTimeList: number[];
}
function useFoo({ initialMessage, messageList, messageTimeList }: Props) {
if (messageList.length === messageTimeList.length) {
console.log('I want to must be same');
}
}
// SomeComponent.tsx
function SomeComponent() {
useFoo({ initialMessage: 'hello world', messageList: ['h', 'e', 'l'], messageTimeList: [1, 2] });
// I want to be this situation call error
}
I want to using at react hook like this code.
How to check two props length in interface ?
If you care about tracking array length in the type system, you probably want to use tuple types, where the exact length and order of array elements is specified. So a string[] can have any number of strings in any order, but a ["h", "e", "l"] must have exactly three elements whose values are the string literals "h", "e", and "l", in that order.
Assuming you want to be able to support any length of messageList, then you can't really write out Props as a specific type; you'd need something like an infinite union like {messageList: [], messageTimeList: []} | {messageList: [string], messageTimeList: [number]} | {messageList: [string, string], messagetimeList: [number, number]} | .... Instead, you probably want Props to be generic in T, the type of messageList, and then use tuple mapping to express that messageTimeList should be the same shape as T except that its elements should be number instead of string. It looks like this:
interface Props<T extends string[]> {
initialMessage: string;
messageList: [...T];
messageTimeList: [...{ [I in keyof T]: number }];
}
Note that I've wrapped both messageList and messageTimeList properties in [... ]; that's a variadic tuple type and I'm only doing it here to give the compiler a hint that you'd like it to infer tuple types for T instead of plain array types.
Anyway, Props<["h", "e", "l"]> would allow only ["h", "e", "l"] for messageList, but for messageTimeList it would allow [number, number, number]. You don't want to have to write out Props<["h", "e", "l"]> manually, and luckily you don't have to... you can write useFoo() to also be generic in T and have the compiler infer T for you when you call it:
function useFoo<T extends string[]>(
{ initialMessage, messageList, messageTimeList }: Props<T>
) {
if (messageList.length === messageTimeList.length) {
console.log('I want to must be same');
}
}
And let's test it out:
useFoo({
initialMessage: 'hello world',
messageList: ['h', 'e', 'l'],
messageTimeList: [1, 2] // error!
//~~~~~~~~~~~~~ <--
// Source has 2 element(s) but target requires 3.
});
useFoo({
initialMessage: "abc",
messageList: ['a', 'b', 'c'],
messageTimeList: [1, 2, 3]
}) // okay
Looks good!
Playground link to code

Typescript Typeguard check if array is of type

I want to write a typeguard to check if all children of array are of type T thus making it an Array where T is a generic type
TS Playground
// Assume arr of any type but Array
const arr: any[] = [
{
foo: "bleh1",
bar: 1
},
{
foo: "bleh2",
bar: 2
},
]
interface newType {
foo: string
bar: number
}
// Check that arr is an array of newType , ie arr: newType[]
const isArrayOf = <T,>(arr: any): arr is Array<T> => {
// TypeScript mastery needed here
return true
}
if(isArrayOf<newType>(arr)){
arr
}
The best thing you could do is this:
const arr: NewType[] = [
{
foo: "bleh1",
bar: 1
},
{
foo: "bleh2",
bar: 2
},
]
interface NewType {
foo: string
bar: number
}
type TypeOfArrayElements<T> = T extends Array<infer U> ? U : never;
type ArrayType = TypeOfArrayElements<typeof arr>;
TypeScript will never be able to guess that the array typed as any[] actually contains NewType elements. Like everything in TypeScript, type predicates are static and won't return a type dynamically based on what is passed as a parameter at runtime. But if you type it as NewType[], then you can extract the NewType type from it.
Edit 1:
This answer has two shortcomings (mentioned in comments)
The property needs not be own for it to exist on the object x = {a: 1}; y = Object.create(x); y.b = 2
The function requires you to manually enumerate all the keys of the object. Thus can introduce human error
I think the solution can still be used as workaround in specific circumstances
Original:
If an array a is of type newType[] then every element of a , consider x = a[0] will be of type newType. x is of type newType because x satisfies all properties and methods of type newType.
Thus if reversed , if x y z are of type newType and they are the only and all elements of array a, Thus every element of a are of type newType, which satisfies the condition for a to be of type newType[]
// Check if obj has all keys props[]
const hasAllProperties = <T,>(obj: any, props: (keyof T)[]): obj is T => {
return props.every((prop) => {
// console.log(prop)
return Object.prototype.hasOwnProperty.call(obj, prop)})
}
// Check that arr is an array of newType , ie arr: newType[]
const isArrayOf = <T,>(obj: any[], props: (keyof T)[]): obj is T[] => {
// Check if every elements have all keys in props
return obj.every((ele) => {
// console.log(ele)
return hasAllProperties<T>(ele,props)
}
)
}
if (isArrayOf<newType>(arr, ["foo", "bar"])) {
console.log("arr is of newType[]")
}
TS Playground

TS7053: Element implicitly has an 'any' type because expression of type 'string' can't be used to index type 'rowType' [duplicate]

When using Object.keys(obj), the return value is a string[], whereas I want a (keyof obj)[].
const v = {
a: 1,
b: 2
}
Object.keys(v).reduce((accumulator, current) => {
accumulator.push(v[current]);
return accumulator;
}, []);
I have the error:
Element implicitly has an 'any' type because type '{ a: number; b: number; }' has no index signature.
TypeScript 3.1 with strict: true. Playground: here, please check all checkboxes in Options to activate strict: true.
Object.keys returns a string[]. This is by design as described in this issue
This is intentional. Types in TS are open ended. So keysof will likely be less than all properties you would get at runtime.
There are several solution, the simplest one is to just use a type assertion:
const v = {
a: 1,
b: 2
};
var values = (Object.keys(v) as Array<keyof typeof v>).reduce((accumulator, current) => {
accumulator.push(v[current]);
return accumulator;
}, [] as (typeof v[keyof typeof v])[]);
You can also create an alias for keys in Object that will return the type you want:
export const v = {
a: 1,
b: 2
};
declare global {
interface ObjectConstructor {
typedKeys<T>(obj: T): Array<keyof T>
}
}
Object.typedKeys = Object.keys as any
var values = Object.typedKeys(v).reduce((accumulator, current) => {
accumulator.push(v[current]);
return accumulator;
}, [] as (typeof v[keyof typeof v])[]);
Based on Titian Cernicova-Dragomir answer and comment
Use type assertion only if you know that your object doesn't have extra properties (such is the case for an object literal but not an object parameter).
Explicit assertion
Object.keys(obj) as Array<keyof typeof obj>
Hidden assertion
const getKeys = Object.keys as <T extends object>(obj: T) => Array<keyof T>
Use getKeys instead of Object.keys. getKeys is a ref to Object.keys, but the return is typed literally.
Discussions
One of TypeScript’s core principles is that type checking focuses on the shape that values have. (reference)
interface SimpleObject {
a: string
b: string
}
const x = {
a: "article",
b: "bridge",
c: "Camel"
}
x qualifies as a SimpleObject because it has it's shape. This means that when we see a SimpleObject, we know that it has properties a and b, but it might have additional properties as well.
const someFunction = (obj: SimpleObject) => {
Object.keys(obj).forEach((k)=>{
....
})
}
someFunction(x)
Let's see what would happen if by default we would type Object.keys as desired by the OP "literally":
We would get that typeof k is "a"|"b". When iterating the actual values would be a, b, c. Typescript protects us from such an error by typing k as a string.
Type assertion is exactly for such cases - when the programmer has additional knowledge. if you know that obj doesn't have extra properties you can use literal type assertion.
See https://github.com/microsoft/TypeScript/issues/20503.
declare const BetterObject: {
keys<T extends {}>(object: T): (keyof T)[]
}
const icons: IconName[] = BetterObject.keys(IconMap)
Will retain type of keys instead of string[]
1. npm install ts-extras (written by sindresorhus)
Use it:
import { objectKeys } from 'ts-extras'
objectKeys(yourObject)
That's it.
====
Here's another pkg I made before I knew about ts-extras:
npm install object-typed --save
import { ObjectTyped } from 'object-typed'
ObjectTyped.keys({ a: 'b' })
This will return an array of type ['a']
I completely disagree with Typescript's team's decision...
Following their logic, Object.values should always return any, as we could add more properties at run-time...
I think the proper way to go is to create interfaces with optional properties and set (or not) those properties as you go...
So I simply overwrote locally the ObjectConstructor interface, by adding a declaration file (aka: whatever.d.ts) to my project with the following content:
declare interface ObjectConstructor extends Omit<ObjectConstructor, 'keys' | 'entries'> {
/**
* Returns the names of the enumerable string properties and methods of an object.
* #param obj Object that contains the properties and methods. This can be an object that you created or an existing Document Object Model (DOM) object.
*/
keys<O extends any[]>(obj: O): Array<keyof O>;
keys<O extends Record<Readonly<string>, any>>(obj: O): Array<keyof O>;
keys(obj: object): string[];
/**
* Returns an array of key/values of the enumerable properties of an object
* #param obj Object that contains the properties and methods. This can be an object that you created or an existing Document Object Model (DOM) object.
*/
entries<T extends { [K: Readonly<string>]: any }>(obj: T): Array<[keyof T, T[keyof T]]>
entries<T extends object>(obj: { [s: string]: T } | ArrayLike<T>): [string, T[keyof T]][];
entries<T>(obj: { [s: string]: T } | ArrayLike<T>): [string, T][];
entries(obj: {}): [string, any][];
}
declare var Object: ObjectConstructor;
Note:
Object.keys/Object.entries of primitive types (object) will return never[] and [never, never][] instead of the normal string[] and [string, any][]. If anyone knows a solutions, please, feel free to tell me in the comments and I will edit my answer
const a: {} = {};
const b: object = {};
const c: {x:string, y:number} = { x: '', y: 2 };
// before
Object.keys(a) // string[]
Object.keys(b) // string[]
Object.keys(c) // string[]
Object.entries(a) // [string, unknown][]
Object.entries(b) // [string, any][]
Object.entries(c) // [string, string|number][]
// after
Object.keys(a) // never[]
Object.keys(b) // never[]
Object.keys(c) // ('x'|'y')[]
Object.entries(a) // [never, never][]
Object.entries(b) // [never, never][]
Object.entries(c) // ['x'|'y', string|number][]
So, use this with caution...
You can use the Extract utility type to conform your param to only the keys of obj which are strings (thus, ignoring any numbers/symbols when you are coding).
const obj = {
a: 'hello',
b: 'world',
1: 123 // 100% valid
} // if this was the literal code, you should add ` as const` assertion here
// util
type StringKeys<objType extends {}> = Array<Extract<keyof objType, string>>
// typedObjKeys will be ['a', 'b', '1'] at runtime
// ...but it's type will be Array<'a' | 'b'>
const typedObjKeys = Object.keys(obj) as StringKeys<typeof obj>
typedObjKeys.forEach((key) => {
// key's type: 'a' | 'b'
// runtime: 'a', 'b', AND '1'
const value = obj[key]
// value will be typed as just `string` when it's really `string | number`
})
All that said, most developers would probably consider having numbers as keys a poor design decision/bug to be fixed.
Here is a pattern I use for copying objects in a typesafe way. It uses string narrowing so the compiler can infer the keys are actually types. This was demonstrated with a class, but would work with/between interfaces or anonymous types of the same shape.
It is a bit verbose, but arguably more straightforward than the accepted answer. If you have to do the copying operation in multiple places, it does save typing.
Note this will throw an error if the types don't match, which you'd want, but doesn't throw an error if there are missing fields in thingNum. So this is maybe a disadvantage over Object.keys.
class thing {
a: number = 1;
b: number = 2;
}
type thingNum = 'a' | 'b';
const thingNums: thingNum[] = ['a', 'b'];
const thing1: thing = new thing();
const thing2: thing = new thing();
...
thingNums.forEach((param) => {
thing2[param] = thing1[param];
});
playground link
Here's a more accurate utility function:
const keys = Object.keys as <T>(obj: T) =>
(keyof T extends infer U ? U extends string ? U : U extends number ? `${U}` : never : never)[];
Explanation: keyof T extends string | number | symbol, however Object.keys omits the symbol keys and returns number keys as strings. We can convert number keys to string with a template literal `${U}`.
Using this keys utility:
const o = {
x: 5,
4: 6,
[Symbol('y')]: 7,
};
for(const key of keys(o)) {
// key has type 'x' | '4'
}
As a possible solution, you can iterate using for..in over your object:
for (const key in myObject) {
console.log(myObject[key].abc); // works, but `key` is still just `string`
}
While this, as you said, would not work:
for (const key of Object.keys(myObject)) {
console.log(myObject[key].abc); // doesn't!
}

Passing an array to a spread operator parameter with TypeScript

I've got a class that inherits from Array:
class List<T> extends Array<T> {
constructor(items: T[]) {
super(items); // error here...
}
}
Argument of type 'T[]' is not assignable to parameter of type 'T'.
Assuming this is because Array's constructor takes (...items: T[])
So, how do I pass a standard array to something that takes a spread operator?
The Array Constructor takes any number of parameters. If you pass in an array into new Array() it gets assigned to the first index.
const newArray = new Array([1, 2, 3]);
console.log(newArray); // [[1, 2, 3]];
Use the spread operator to expand the array out to the parameters.
const newArray = new Array(...[1, 2, 3]); // equivalent to new Array(1, 2, 3);
console.log(newArray); // [1, 2, 3];
So your class would look like this:
class List<T> extends Array<T> {
constructor(items: T[]) {
super(...items); // Spread operator here
}
}

Set type of an array when initializing a class

I have a class called Controller that contains one Array property. Right now, my class is declared like that:
class Controller {
var myArray: [AnyObject]
init(bool: Bool) {
if bool == true {
myArray = [10, 11, 12]
} else {
myArray = ["Yo", "Ya", "Yi"]
}
}
}
The problem that I have with this code is that myArray is still (of course) of type [AnyObject] after my class initialization. Thus, every time I need to get an object out of myArray, I have to cast its type (Int or String) just like this:
let controller = Controller(bool: false)
let str = controller.array[0] as String
I want to be able to write let str = controller.array[0] //str: String without having to cast the real type of the objects inside myArray. Is there a way to do so? Do I have to use lazy init, struct, generic types?
Here is a attempt in pseudo code:
class Controller {
var myArray: Array<T> //Error: use of undeclared type 'T'
init(bool: Bool) {
if bool == true {
myArray = [10, 11, 12] as [Int] //myArray: Array<Int>
} else {
myArray = ["Yo", "Ya", "Yi"] as [String] //myArray: Array<String>
}
}
}
So as Oscar and Elijah pointed out (up votes to them), I am just trying to be a little more verbose here. You need to declare the generic, T, when you define the class.
This would mean you need to define what that generic's type is at initialization of the class.
class Foo<T> {
var items = [T]()
}
let stringFoo = Foo<String>()
stringFoo.items.append("bar")
stringFoo.items[0] = "barbar"
stringFoo.items // ["barbar"]
let intFoo = Foo<Int>()
intFoo.items.append(1)
intFoo.items[0] = 11
intFoo.items // [11]
So in your case, rather than passing a Bool for the init method, just define the generic's type at initialization instead.
class Controller<T> //You missed that T, right?
{
var myArray: Array<T>
/* initialization stuff */
}
var c = Controller<String>()
c.myArray[0] = "Hey!"
let str = c.myArray[0] //String, eventually!
class Controller<T> {
var array: Array<T> = []
...
}

Resources