Use typescript to force objects in array to have same fields for all indexes in array - arrays

I have run into a scenario where I would like the following type-enforcement:
I want the type MyArrayType to be an array of objects. The fields in the objects must be identical for each object-index in the array, but the 'catch' is that I do not know the fields beforehand. I would like them to be 'inferred'.
Here is an example of how the typing would work:
const myArrayType: MyArrayType = [{ foo: 2 }, { foo: 1 }] // this will work
const myArrayType: MyArrayType = [{ foo: 2 }, { bar: 1 }] // this will fail
const myArrayType: MyArrayType = [{ bar: 2 }, { bar: 1 }] // this will work
const myArrayType: MyArrayType = [{ anyFieldName: 2 }, { anyFieldName: 1 }] // this will work
const myArrayType: MyArrayType = [{ bar: 2, foo: 1 }, { bar: 1, foo: 2 }] // this will work
const myArrayType: MyArrayType = [{ bar: 2, foo: 1 }, { bar: 1 }] // this will fail
The following should also fail:
const myArrayType: MyArrayType = [1,2,3].map((number) => {
if (number === 2) {
return {
foo: 1
bar: 2
}
}
return {
bar: 2
}
})
And this should work:
const myArrayType: MyArrayType = [1,2,3].map((number) => {
return {
foo: 1
bar: 2
}
})
Is this possible to achieve with typescript as of 2021?
Additional constraints:
Compile-time checking. Runtime checking is useful but not required.
Wrapper functions and helpers are OK.
Ideally, the function applies excess property checking as well, throwing errors if unexpected extra properties are present on values beyond the first.

You can express the first part of this with this generic function:
function check<T1, T2 extends T1>(t1: T1, ...tRest: T2[]): T1[] {
return [t1, ...tRest];
}
// Accepts an entire array, courtesy Jeff Mercado (thank you!).
function checkArray<T1, T2 extends T1>(arr: [T1, ...T2[]]): T1[] {
return arr;
}
Playground Link
As far as the map is concerned, TypeScript correctly infers the union return type of the callback function. Your best bet there is probably to "prohibit" a union by forcing the type to never, as you can do with existing tools like this example by Titian Cernicova-Dragomir:
type UnionToIntersection<U> =
(U extends any ? (k: U)=>void : never) extends ((k: infer I)=>void)
? I : never
type NoUnion<Key> =
// If this is a simple type UnionToIntersection<Key> will be the same type,
// otherwise it will an intersection of all types in the union and probably
// will not extend `Key`
[Key] extends [UnionToIntersection<Key>] ? Key : never;

Related

Use typescript to create object from array of objects

I have the following typescript code:
const a = [{ foo: 1 }, { bar: '2' }]
I wish to use a to create an object of the form:
const b = {
foo: 1,
bar: '2'
}
and the typing of b should be equivalent to the type:
type EquivalentType = {
foo: number
bar: string
}
Is this possible without casting? How can this be done?
Sure there is. This solution does not need as const like #Vija02's (although it is perfectly fine if it does).
Map over all possible keys in the array, then get only the type of that key using Extract:
type CreateFrom<T extends ReadonlyArray<unknown>> = { [K in keyof T[number]]-?: Extract<T[number], { [_ in K]: any }>[K] };
Then you'd just use this type in a supposed function:
function createFrom<T extends ReadonlyArray<unknown>>(list: T): CreateFrom<T> {
// ... for you to implement!
}
Note that you might need to cast the return type. I don't think TypeScript will be too happy with this one.
And to finish it off, this is a playground demonstrating the solution.
// You might be able to simplify this
type TypeFromLiteral<T> = T extends string ? string : T extends number ? number : T extends boolean ? boolean : never;
// The "as const" part is important so that we can process the types
const a = [{ foo: 1 }, { bar: '2' }] as const;
// Get the final type
type ObjectUnion = typeof a[number];
type NewType = { [T in ObjectUnion as keyof T]: TypeFromLiteral<T[keyof T]> };
// By itself, this will get the correct value. However, we need to process the type separately and cast it to get what you want.
const b = Object.assign({}, ...a) as NewType;

Typescript: problem with intersection of types containing a subset of specialized arrays

I've a problem with intersection of types containing array members whose type is a subset of a specific type. It's harder to explain for me than simply show you the code.
This is a custom type
type MyType = 'A' | 'B' | 'C' | 'D';
This is the generic interface for the "base" object
interface Base<A = {}, B = {}> {
stringMember: string;
arrayMember: MyType[];
objectMember: {
something: boolean;
} & A;
b: B;
}
I need to specialize arrayMember, objectMember and b memebers like so
type A_Object_1 = {
somethingElse: number;
};
type B_Object_1 = {
aMember: boolean;
};
// Specialized type 1
type Specialized_1 = Base<A_Object_1, B_Object_1> & {
arrayMember: ['A'];
};
// Specialized type 1 object, this is OK
const a_OK: Specialized_1 = {
stringMember: 'hi',
arrayMember: ['A'],
objectMember: {something: true, somethingElse: 3},
b: {aMember: true},
};
and so
type A_Object_2 = {
anything: boolean;
};
type B_Object_2 = {
bMember: number;
};
type Specialized_2 = Base<A_Object_2, B_Object_2> & {
arrayMember: ['B'];
};
// Specialized type 2 object, this is OK
const b_OK: Specialized_2 = {
stringMember: 'hello',
arrayMember: ['B'],
objectMember: {something: true, anything: true},
b: {bMember: 3},
};
I need an intersection type between Specialized_1 and Specialized_2, it should be like this
{
stringMember: string;
arrayMember: ['A', 'B'];
objectMember: {
something: boolean;
somethingElse: number;
anything: boolean;
};
b: {aMember: boolean; bMember: number};
}
I do that the following (maybe wrong) way
type Specialized_3 = Specialized_1 & Specialized_2;
The members that are objects are ok, since they contain both members from Specialized_1 & Specialized_2
Anyway there is a problem with the array type that is never, I see why, there is no intersection between ['A'] and ['B'],
const c_Problem: Specialized_3 = {
stringMember: 'hi there',
arrayMember: ['A', 'B'], // Error here because arrayMember: never
objectMember: {something: true, somethingElse: 3, anything: false}, // OK
b: {aMember: false, bMember: 8}, // OK
};
It's been 2 days I'm thinking about that and I can't figure it out...
Moreover I find difficult to find the search keywords for this very specific problem.
Any help?
EDIT:
Thanks to #AlekseyL.I made some progress, I can use Omit to omit arrayMember from the intersected object and than union it later "manually"
I can do this using a generic type like so:
type PossibleSolution<T1 extends Base, T2 extends Base> = Omit<T1 & T2, 'arrayMember'> & {
arrayMember: [T1['arrayMember'][number], T2['arrayMember'][number]];
};
type Specialized_Test = PossibleSolution<Specialized_1, Specialized_2>;
const d_OK: Specialized_Test = {
stringMember: 'hi there',
arrayMember: ['A', 'B'], // OK
objectMember: {something: true, somethingElse: 3, anything: false}, // OK
b: {aMember: false, bMember: 8}, // OK
};
The use of the generic type is for convenience since I need to reuse that here and there in my code.
This solution works, but only to intersect 2 objects, if I need to intersect 3 objects I have to delcare a different generic type
Now the problem is, how can I modify PossibileSolution generic type to intersect N SpecializedObjects?

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

Type definition for array with objects in TypeScript

How can be defined types in TypeScript for en such array:
export const AlternativeSpatialReferences: Array< ??? > = [
{
'25833': 25833
},
{
'25832': 25832
},
{
'25831': 25831
},
{
'Google': 4326
}
];
Now I just use Array<{}>, but want define properly.
If you want to define an object which property names are not known at compile time and which values are numbers, you should use an "index signature" (Thanks #Joe Clay):
interface MyObject {
[propName: string]: number;
}
Then you can write:
export const AlternativeSpatialReferences: MyObject[] = [
{
'25833': 25833
},
{
'25832': 25832
},
{
'25831': 25831
},
{
'Google': 4326
}
];
in typescript you make use of any type ,
any used for - need to describe the type of variables that we do not know when we are writing an application.
Array<any>
if you want to go for some strong type than you should create new class with two property
public class KeyValue
{
key:string;
value:number;
}
let myarray: KeyValue[] = new Array<KeyValue>();
myarray.push({key: '25833' , value : 25833});
myarray.push({key: 'Google' , value : 123});
and convert your current array values in strong type.

typescript multidimensional array with different types

I have an custom object declared (ThreadObj) and I want to create a THREADLISTS, holding multiple arrays of Threadlist. So
Threadlist:ThreadObj[]=[];
THREADLISTS:[ThreadObj[]][ThreadObj]=[][]; //how to type and init?
The first dim is of ThreadObj[] and the second is of ThreadObj.
Cheers
Example :
type ThreadObj = {foo:string}
type ThreadList = ThreadObj[];
type ThreadListList = ThreadList[];
const obj: ThreadObj = {
foo: '123'
}
const singleDim: ThreadList = [
obj
]
const multiDim: ThreadListList = [
singleDim,
singleDim
]
More
All in one step:
const allInOneStep: {foo:string}[][] = [
[
{
foo: 'hello'
},
{
foo: 'is it me'
}
],
[
{
foo: 'you are looking for'
}
]
]
Wouldn't that just be:
let arr:ThreadObj[][] = []
For multi-dimensional array in typescript, you can simply declare and define the variable as
let multiArr:(string|number)[][] = [["Ram","Shyam",1,2,3,"Hari"]];

Resources