Reputation: 129
I have the following fixture file that i have type guarded below. it has few optional properties
fixture file-
{
"profiles": [
{
"name": "Laakea",
"phoneNumber": "2033719225",
"authGroupName": "Drivers"
},
{
"name": "Lkhagvasuren",
"phoneNumber": "2033719225",
"authGroupName": "Drivers"
},
{
"name": "Joaquin",
"phoneNumber": "2033719225"
}
]
}
type interface-
export interface Profile {
name: string;
authGroupName?: string;
phoneNumber?: string;
email?: string;
}
type guard function-
export function isValidProfiles(profiles: unknown): profiles is Profile[] {
if (!Array.isArray(profiles)) {
return false;
}
for (let index = 0; index < profiles.length; index += 1) {
if (typeof profiles[index].name !== 'string') {
return false;
}
if (profiles[index].email) {
if (typeof profiles[index].email !== 'string') {
return false;
}
}
if (profiles[index].phoneNumber) {
if (typeof profiles[index].phoneNumber !== 'string') {
return false;
}
}
if (profiles[index].authGroupName) {
if (typeof profiles[index].authGroupName !== 'string') {
return false;
}
}
}
return true;
}
i was wondering if i could write it better instead of all these if statements ?
Upvotes: 2
Views: 972
Reputation: 328272
Given that you have essentially identical code for the checks against the email
, phoneNumber
, and authGroupName
properties, you can refactor those checks into a single piece of code that gets run multiple times.
For example, you can make an array of the keys (strongly typed via a const
assertion so the compiler remembers that the values in them are literally "email"
, "phoneNumber"
, and "authGroupName"
as opposed to just string
), and then use its every()
method to return true
if and only if the check works for every key, for every member of the profiles
array:
function isValidProfiles(profiles: unknown): profiles is Profile[] {
if (!Array.isArray(profiles)) { return false; }
const keys = ["email", "phoneNumber", "authGroupName"] as const;
return profiles.every(p => p && (typeof p === "object") && (typeof p.name === "string") &&
keys.every(k => typeof p[k] === "undefined" || typeof p[k] === "string")
);
}
Here the check I'm using is that, for each key k
, and for each profile p
, typeof p[k]
is either "undefined"
or "string"
. This will correctly deal with string
properties and missing properties. It will also accept a property whose key is present but whose value is explicitly undefined
. That might not be what you consider "optional", but TypeScript does by default... unless you explicitly enable the --exactOptionalPropertyTypes
compiler option... which isn't even part of the "standard" --strict
suite of compiler features.
Okay, let's test it:
const val = [
{
"name": "Laakea",
"phoneNumber": "2033719225",
"authGroupName": "Drivers"
},
{
"name": "Lkhagvasuren",
"phoneNumber": "2033719225",
"authGroupName": "Drivers"
},
{
"name": "Joaquin",
"phoneNumber": "2033719225"
}
];
if (isValidProfiles(val)) {
console.log(val.map(
x => x.authGroupName ?? "no-auth").join(",")
); // "Drivers,Drivers,no-auth"
} else {
console.log("nope")
}
Looks good. The compiler validates val
as being a Profile[]
and can treat is as such.
Upvotes: 1
Reputation: 129
came up with this -
export function isValidProfiles(profiles: unknown): profiles is Profile[] {
if (!Array.isArray(profiles)) {
return false;
}
for (let index = 0; index < profiles.length; index += 1) {
if (
!(
typeof profiles[index].name === 'string' &&
(!profiles[index].phoneNumber || typeof profiles[index].phoneNumber === 'string') &&
(!profiles[index].email || typeof profiles[index].email === 'string') &&
(!profiles[index].authGroupName || typeof profiles[index].authGroupName === 'string')
)
) {
return false;
}
}
return true;
}
this is no way as concrete and powerful as the function written by @jcalz.
Upvotes: 0