I am new to typescript and I was wondering if there is a more efficient way to check for null values in an object besides using a for loop? Example below:
interface testType {
one: string | null;
two: string | null;
three: string | null;
four: string | null;
}
const dict: testType = {
one: "hello",
two: "world",
three: null,
four: "!",
};
let field: keyof testType;
for (field in dict) {
if (dict[field] == null) {
console.log('null');
} else {
console.log('exist');
}
}
CodePudding user response:
Object.values()
and some()
will combine nicely to do it...
Object.values(testType).some(value => value === null)
Docs: Object.values() and some
CodePudding user response:
every works too. You can increase solutions with standard built-in methods.
const isNull = Object.values(object).every(x => (x === null || x === ''));
CodePudding user response:
Iteration is required in this scenario, so some kind of "loop" is the only way to check each value.
The only type-safe way to iterate the expected keys (or values at the expected keys) in an object in TypeScript is to maintain a list of the keys and use them for iteration. In the example below, I use an array of keys (initialized with a const
assertion) along with the type utility Record<Keys, Type>
to build the type of the interface in your question. Then the array of keys is used to iterate the object annotated by the same type:
const keys = ['one', 'two', 'three', 'four'] as const;
type DictKey = typeof keys[number];
//^? type DictKey = "one" | "two" | "three" | "four"
type Dict = Record<DictKey, string | null>;
/* ^ This type looks like:
type Dict = {
one: string | null;
two: string | null;
three: string | null;
four: string | null;
}
*/
let dict: Dict = {
one: "hello",
two: "world",
three: null,
four: "!",
};
for (const key of keys) {
const value = dict[key];
if (typeof value === 'string') {
console.log(`${key} exists:`, value);
//^? const value: string
} else {
console.log(`${key} is ${value}`);
//^? const value: null
}
}
The reason for this is that TypeScript is structurally-typed, so it allows for extra (excess) properties in object values assigned to an object type — as long as all the expected properties are there and their values are of the correct type.
const anotherValueThatIsAssignableToDict = {
one: 'goodnight',
two: 'moon',
three: null,
four: null,
booleanProp: false,
numberProp: 22,
stringProp: 'unexpected',
};
dict = anotherValueThatIsAssignableToDict; // TypeScript is ok with this
Trying to iterate the keys of an object type using key in object
or Object.keys
will always result in string
key types:
for (const key in dict) {
const value = dict[key]; /*
~~~~~~~~~
Element implicitly has an 'any' type because expression of type 'string' can't be used to index type 'Dict'.
No index signature with a parameter of type 'string' was found on type 'Dict'.(7053) */
}
And, if you simply iterate the values in the object (like in this answer), you'll get all of the excess values, too, which is very unlikely what you want:
for (const value of Object.values(dict)) {
console.log(value); // Iteratively logs each value in "anotherValueThatIsAssignableToDict"
}
So, maintaining a list of the keys yourself is the only type-safe way, and it is also more performant because it skips iterating the excess entries in the object.