Issue
I have the following typescript code:
type StrictPropertyCheck<T, TExpected> = Exclude<keyof T, keyof TExpected> extends never ? Record<string, unknown> : 'TypeWithStrictPropertyCheck';
export const check = <
T1 extends Record<string, string | string[]>,
T2 extends T1 & StrictPropertyCheck<T2, T1>,
>(arg: {
myKey: [T1, ...T2[]]
}): {
myKey: T1[]
} => {
return arg
}
export const template = check({
myKey: [{
a: '',
b: [''],
c: ['', '']
}, {
a: '',
b: [''],
c: ['', '']
}]
})
The check function checks that all the objects in the array myKey has the same keys and key types for each index.
How can I instead of explicitly typing myKey in the function arg input, accept any number of keys with any given name, and still have the same check performed for each array related to the individual key?
In short; I would like arg to accept any key instead of an explicit one.
EDIT:
The array for each arg-key should consist of objects, where the key-names in every object is exactly the same as the key-names in the first object in the array. Additionally, the key-type of each object-key in every object, must match the key-type of the corresponding key-name in the first object. The key-types in each object can either be string[] or string.
(this is what the check function does, just not for any arg-key)
Solution
One approach you can take is to turn your T1 and T2 type parameters into object types where for each property key K in keyof T1, the property values T1[K] and T2[K] are like your old T1 and T2 values. That yields this code:
export const check = <
T1 extends { [K in keyof T1]: Record<string, string | string[]> },
T2 extends { [K in keyof T1]: T1[K] & StrictPropertyCheck<T2[K], T1[K]> },
>(arg: { [K in keyof T1]: [T1[K], ...T2[K][]] }) => arg;
It works, I think:
check({
myKey: [{
a: '',
b: [''],
c: ['', ''],
}, {
a: '',
b: [''],
c: ['', ''],
}]
}); // okay
check({
myKey: [{ /* error!
~~~~~ <-- Types of property 'a' are incompatible. */
a: [''],
}, {
a: '',
}]
})
check({
myKey: [{ /* error!
~~~~~ <-- Property 'd' does not exist on type... */
a: '',
b: [''],
c: ['', ''],
}, {
a: '',
b: [''],
c: ['', ''],
d: [""]
}]
})
check({
myKey: [{ /* error!
~~~~~ <-- Property 'b' is missing in type... */
a: [''],
b: ''
}, {
a: ['']
}]
})
The only problem I have with it is that the errors appear in a slightly weird place. Still, this is more or less a direct extension of your version.
Another approach is to step back and have arg be of a single type parameter type T, and then come up with constraints that walk through the properties of T and make sure that they conform to your rules. This is a little easier for the compiler to infer, since you're just asking for T given a value of type T, instead of T1 and T2 given a value of type {[K in keyof T1]: [T1[K], ...T2[K][]] }. Here it is:
// give names to types used more than once
type StringBag = Record<string, string | readonly string[]>;
type StringBagArray = readonly [StringBag, ...StringBag[]];
type StrictCheckSubsequentProperties<T extends StringBagArray> = {
[I in keyof T]: T[0] & {
[K in keyof T[I]]: K extends keyof T[0] ? T[I][K] : 'TypeWithStrictPropertyCheck'
}
};
export const check = <T extends { [K in keyof T]:
StringBagArray & StrictCheckSubsequentProperties<T[K]>
}>(arg: T) => arg;
This works by checking that each property in arg is a StringBagArray (an array of objects whose properties are string or string[]) and that all elements of that array are assignable to the type of the first element, and that any extra keys are of type 'TypeWithStrictPropertyCheck' (we could also have written never or something else there).
And it also works:
check({
myKey: [{
a: '',
b: [''],
c: ['', ''],
}, {
a: '',
b: [''],
c: ['', ''],
}]
}) // okay
check({
myKey: [{
a: [''],
}, {
a: '', /* error!
~ <-- Type 'string' is not assignable to type 'string[]' */
}]
})
check({
myKey: [{
a: '',
b: [''],
c: ['', ''],
}, {
a: '',
b: [''],
c: ['', ''],
d: [""] /* error!
~ <-- string[] is not assignable to "TypeWithStrictPropertyCheck" */
}]
})
check({
myKey: [{
a: [''],
b: ''
}, { // error!
a: ['']
}] /*
~~~~~~~~~~~~~~~~~~~ <-- Property 'b' is missing in type...
*/
})
I prefer the error locations in this version, which tend to be more localized to the place where the problem is. But it's ultimately up to you to decide which version, if any, meets your needs.
Answered By - jcalz
0 comments:
Post a Comment
Note: Only a member of this blog may post a comment.