Suppose that we have this Typescript code, what could be the best way to code the TODO part ? My team and I are looking for an elegant way to do that
type MyType = {
key1?: string | null
key2?: string | null
key3?: string | null
// ...
// ...
// ...
};
type MyTypeNotNull = {
key1?: string
key2?: string
key3?: string
// ...
// ...
// ...
};
function logMyType(type: MyTypeNotNull) {
console.log(type);
}
const myType: MyType = {
key1: 'myKey1',
// key2 is omitted
key3: null,
// ...
// ...
// ...
};
const run = (value: MyType) => {
/* TODO
- throw when a prop is null
- make myType pass the type check of logMyType
*/
logMyType(value); // Error Type 'string | null | undefined' is not assignable to type 'string | undefined'
};
run(myType);
Currently we have a bunch of if following each other, and feel that it should exist a better way to do that, do you have any clean suggestions ?
CodePudding user response:
You could write a custom assertion function that iterates over all its argument's properties and throws if any of them are null
; and the returned assertion predicate (of the form asserts xxx is YYY
) would narrow the type of the argument to a version with all non-null properties. Here's one approach:
function assertNoPropsAreNull<T extends object>(obj: T): asserts obj is
{ [K in keyof T]: Exclude<T[K], null> } {
Object.entries(obj).forEach(([k, v]) => {
if (v === null)
throw new Error("OH NOEZ, PROP \"" k "\" IS NULL");
});
}
The implementation of assertNoPropsAreNull()
uses the Object.entries()
method to get an array of key/value pairs for the arg
argument, and throws an error if it finds any where the value is null (and mentions the key in the error message).
The call signature of assertNoPropsAreNull()
is generic in T
, the type of obj
, and the return type is asserts obj is { [K in keyof T]: Exclude<T[K], null> }
. That's a mapped type which just strips null
out of each property type via the Exclude<T, U>
utility type.
Let's test it:
const run = (value: MyType) => {
value; // (parameter) value: MyType
assertNoPropsAreNull(value)
value; /* (parameter) value: {
key1?: string | undefined;
key2?: string | undefined;
key3?: string | undefined;
} */
logMyType(value); // okay
};
The compiler behavior looks good. Before calling assertNoPropsAreNull(value)
, the type of value
is MyType
, and afterwards it has been narrowed to {key1?: string, key2?: string, key3?: string}
. This type is structurally identical to your MyTypeNotNull
type, and thus logMyType()
accepts it with no warnings.
And let's make sure the runtime behavior is also acceptable:
run({ key1: 'myKey1' }); // {key1: "myKey1"}
run({ key1: 'myKey1', key3: null }); //