Exhaustive Property Check in TypeScript
Ensuring that all properties of an object are present and of the correct type is crucial for maintaining code reliability and preventing runtime errors. This challenge asks you to implement a function that performs an exhaustive check against a predefined type definition, reporting any missing or incorrectly typed properties. This is particularly useful when dealing with complex data structures or APIs where strict validation is required.
Problem Description
You are tasked with creating a TypeScript function called exhaustiveCheck that takes two arguments:
obj: An object of typeT.typeDefinition: A TypeScript type definitionT.
The function should iterate through all properties defined in typeDefinition and verify that:
- The property exists on the
obj. - The property's type matches the type defined in
typeDefinition.
If any property is missing or has an incorrect type, the function should throw an error with a descriptive message indicating the missing or mismatched property. If all properties are present and of the correct type, the function should return undefined (or any other suitable indicator of success, but undefined is preferred for clarity).
Key Requirements:
- The function must work with any TypeScript type definition.
- The function must provide clear and informative error messages.
- The function should be robust and handle various data types correctly.
- The function should not modify the input object.
Expected Behavior:
The function should throw an error if any property is missing or has an incorrect type. It should not throw an error if all properties are present and of the correct type.
Edge Cases to Consider:
- Objects with optional properties. The check should not throw an error if an optional property is missing.
- Nested objects. The check should recursively validate nested objects.
- Union types. The check should ensure the property's type is one of the union types.
- Literal types. The check should ensure the property's value matches the literal type.
- Readonly properties. The check should still validate these properties.
Examples
Example 1:
Input:
obj = { name: "John", age: 30 };
typeDefinition = { name: string; age: number; city?: string };
Output:
Error: Property 'city' is missing from object.
Example 2:
Input:
obj = { name: "John", age: "30", city: "New York" };
typeDefinition = { name: string; age: number; city?: string };
Output:
Error: Property 'age' has incorrect type. Expected number, got string.
Example 3:
Input:
obj = { name: "John", age: 30, city: "New York" };
typeDefinition = { name: string; age: number; city?: string };
Output:
undefined
Example 4 (Nested Object):
Input:
obj = { name: "John", address: { street: "123 Main St", city: "Anytown" } };
typeDefinition = { name: string; address: { street: string; city: string } };
Output:
undefined
Example 5 (Union Type):
Input:
obj = { status: "active" };
typeDefinition = { status: "active" | "inactive" };
Output:
undefined
Constraints
- The input object
objcan be of any shape defined by thetypeDefinition. - The
typeDefinitionwill always be a valid TypeScript type definition. - The function should be performant enough to handle reasonably sized objects (up to 100 properties). While performance is not the primary focus, avoid excessively complex or inefficient algorithms.
- The function must be written in TypeScript.
Notes
- Consider using TypeScript's reflection capabilities (e.g.,
typeof,in) to inspect the object's properties and types. - You may need to use type guards to perform type checking.
- Think about how to handle optional properties gracefully.
- Recursive validation is essential for nested objects.
- The goal is to create a robust and reliable exhaustive check function that can be used to validate objects against TypeScript type definitions. Focus on correctness and clarity over extreme optimization.