An alternative to consider is implementing a custom validation solution in TypeScript, tailored to your specific requirements. This approach may require creating a set of type validators for each property and ensuring new fields are properly validated.
- Validate that a property is of a specified type
- Ensure all new fields undergo validation
To meet the second requirement, an object mirroring the structure of the main data object can be created with keys matching the properties of the main object, and their respective types defined within it. The resulting type could then be used for verification purposes by iterating through each key and validating its expected type:
interface Data {
name: string
}
type DataTypes = "string" | "number" | "boolean";
function validateData(data: unknown): Data {
const keyValidators: Record<keyof Data, DataTypes> = {
name: "string"
}
if (typeof data === 'object' && data !== null) {
let maybeData = data as Data
for (const key of Object.keys(keyValidators) as Array<keyof Data>) {
if (typeof maybeData[key] !== keyValidators[key]) {
throw new Error('Invalid data format');
}
}
return maybeData;
}
throw new Error('Invalid data provided');
}
let input: unknown = JSON.parse('{"name": "John"}');
let result = validateData(input);
We can further enhance this system by developing a generic factory function capable of validating any object type, including additional features like handling optional properties or specifying functions for validation:
interface Data {
name: string
optional?: string
}
function allowOptional<T>(as: (s: unknown, errMsg?: string) => T) {
return function (s: unknown, errMsg?: string): T | undefined {
if (s === undefined) return s;
return as(s);
}
}
function expectString(s: unknown, errMsg: string = ""): string {
if (typeof s === "string") return s as string
throw new Error(`${errMsg} '${s} is not a string`)
}
function expectNumber(s: unknown, errMsg?: string): number {
if (typeof s === "number") return s as number;
throw new Error(`${errMsg} '${s} is not a number`)
}
type Validators<T> = {
[P in keyof T]-?: (s: unknown, errMsg?: string) => T[P]
}
function createValidator<T extends object>(keyValidators:Validators<T>) {
return function (data: unknown, errMsg: string = ""): T {
console.log(data);
if (typeof data === 'object' && data !== null) {
let maybeT = data as T
for (const key of Object.keys(keyValidators) as Array<keyof T>) {
keyValidators[key](maybeT[key], errMsg + key + ":");
}
return maybeT;
}
throw new Error(errMsg + 'Invalid data format');
}
}
let inputData: unknown = JSON.parse('{"name": "John"}');
const validateInputData = createValidator<Data>({
name: expectString,
optional: allowOptional(expectString)
})
let outputData = validateInputData(inputData);
interface MoreData {
value: Data
}
const validateMoreData = createValidator<MoreData>({
value: validateInputData
})
let complexData: unknown = JSON.parse('{ "value": {"name": "John"} }');
let processedData = validateMoreData(complexData);
let errorData = validateMoreData(inputData);
Explore Here