Implementing Backward Compatibility in TypeScript with Type Evolution
In software development, maintaining backward compatibility is crucial to ensure that older versions of your code continue to work with newer versions, preventing disruption for users and other systems. This challenge focuses on a common scenario where a data structure evolves over time, and you need to gracefully handle both the old and new formats.
Problem Description
You are tasked with creating a TypeScript function that can process a UserProfile object. The UserProfile structure has undergone an evolution:
- Version 1: Contained
id,name, andemail. - Version 2: Introduced an optional
agefield.
Your function, processUserProfile, should be able to accept and correctly process user profile data in either format. It should extract the common fields (id, name, email) and, if present, the age field. The function should then construct a standardized output object that always includes all fields from both versions, providing a default value for age if it's missing in the input.
Key Requirements:
- Type Definition: Define TypeScript types for both
UserProfileV1andUserProfileV2. - Function Signature: Create a function
processUserProfilethat accepts a parameter that can be eitherUserProfileV1orUserProfileV2. - Data Extraction: Extract
id,name, andemailfrom the input user profile. - Optional Field Handling: If
ageis present in the input (UserProfileV2), extract it. - Standardized Output: Construct an output object with a consistent structure, always including
id,name,email, andage. Ifagewas not provided in the input, use a default value (e.g.,nullorundefined). - Type Safety: Ensure that your implementation leverages TypeScript's type system to prevent runtime errors and provide IntelliSense for developers.
Expected Behavior:
When processUserProfile is called with a UserProfileV1 object, it should produce an output object with the id, name, and email fields, and the age field should be set to its default value.
When processUserProfile is called with a UserProfileV2 object, it should produce an output object with all fields from the input, including age, or its default value if age was explicitly undefined in UserProfileV2.
Examples
Example 1:
// Input V1
const userV1 = {
id: 1,
name: "Alice",
email: "alice@example.com"
};
// Expected Output
{
id: 1,
name: "Alice",
email: "alice@example.com",
age: undefined // or null, depending on chosen default
}
Explanation: The input is a UserProfileV1. The function extracts id, name, and email. Since age is not present in V1, it defaults.
Example 2:
// Input V2 with age
const userV2WithAge = {
id: 2,
name: "Bob",
email: "bob@example.com",
age: 30
};
// Expected Output
{
id: 2,
name: "Bob",
email: "bob@example.com",
age: 30
}
Explanation: The input is a UserProfileV2 with an age. The function extracts all fields, including age.
Example 3:
// Input V2 with undefined age
const userV2UndefinedAge = {
id: 3,
name: "Charlie",
email: "charlie@example.com",
age: undefined // Explicitly undefined
};
// Expected Output
{
id: 3,
name: "Charlie",
email: "charlie@example.com",
age: undefined // or null
}
Explanation: The input is a UserProfileV2 where age is explicitly set to undefined. The function correctly handles this and sets the output age to its default value.
Constraints
- The
idfield will always be a number. - The
nameandemailfields will always be strings. - The
agefield, if present, will be a number orundefined. - The function should be efficient and not introduce significant overhead.
- The solution must be written in TypeScript.
Notes
Consider using a union type for the input parameter to represent both UserProfileV1 and UserProfileV2. Think about how to safely access properties that might not exist in one of the types. A type guard or checking for property existence could be useful here. The choice of default value for age when not provided (e.g., undefined vs. null) is up to you, but be consistent.