If your validation needs are complex enough, I would evaluate the usage of something like io-ts. It is a library used to automatically generate runtime validations based on metadata in your code.
If your needs are more limited, you can just leverage UserDefined Type Guards.
What a type guard does is take an unknown (or any, there is really no difference inside this kind of function) and tells the compiler that the passed in object is compatible with a certain interface.
export interface Field {
icon: string;
text: string;
visibility: boolean;
}
export interface User {
name: Field;
surname: Field;
}
function isField(obj: any): obj is Field {
return (
obj != null &&
typeof obj.icon === "string" &&
typeof obj.text === "string" &&
typeof obj.visibility === "boolean"
);
}
function isUser(obj: any): obj is User {
return obj != null && isField(obj.name) && isField(obj.surname);
// you can get fancy and write something like
// return obj != null && ['name', 'surname'].every(fieldName => isField(obj[fieldName]))
}
// alternative isUser implementation, using a
// prototype. This will give you a compile error is the
// interface is updated, but not this prototype.
const userProto: User = {
name: null,
surname: null
};
function isUserDynamic(obj: any): obj is User {
return obj != null && Object.keys(userProto).every(fieldName => isField(obj[fieldName]));
}
function validateUserArray(obj: any): obj is User[] {
if (obj == null) {
// depending upon the desired approach, you can throw an exception and bail out,
// or simply return false.
throw new Error("The array cannot be null");
}
if (!Array.isArray(obj)) return false;
obj.forEach((user, i) => {
if (!isUser(user))
throw new Error(
`Error at index ${i}: ${JSON.stringify(user)} is not a valid user.`
);
});
return true;
}
const json = `[
{
"name": { "text": "David", "icon": "icon1.png", "visibility": true },
"surname": { "text": "Smith", "icon": "icon2.png", "visibility": true }
},
{
"name": { "text": "Arthur", "icon": "icon3.png", "visibility": true },
"surname": { "text": "L.", "icon": "icon6.png", "visibility": true }
},
{
"name": { "text": "Anthony", "icon": "icon1.png", "visibility": false },
"surname": { "text": "Isaacson", "icon": "icon2.png", "visibility": true }
},
{
"name": { "text": "Mike", "icon": "icon3.png", "visibility": true },
"surname": { "text": "Jobs", "icon": "icon5.png", "visibility": false }
}
]`;
const deserialized: any = JSON.parse(json);
let validatedArray: User[];
if (validateUserArray(deserialized)) {
// here deserialized is a User[], not an any.
validatedArray = deserialized;
}