Recursive Conditional Types for Data Transformation
Recursive conditional types in TypeScript allow you to define types that refer to themselves, enabling powerful data structure transformations and validations. This challenge will test your understanding of this advanced TypeScript feature by requiring you to create a type that recursively transforms a nested object structure based on a provided mapping. This is useful for scenarios like adapting data from one API to another, or enforcing specific data shapes.
Problem Description
You are tasked with creating a TypeScript type called TransformType<T, M> that recursively transforms a type T based on a mapping M. The mapping M is a type that defines how to transform each property of T.
The TransformType<T, M> type should behave as follows:
- If a property
KinTexists as a key inM, then the type ofKin the transformed type should be the type specified inM[K]. - If a property
KinTdoes not exist as a key inM, then the type ofKin the transformed type should be the same as the type ofKinT. - If
Tis an object type, the transformation should be applied recursively to its properties. - If
Tis a primitive type (e.g.,string,number,boolean), orTis a tuple, the transformation should be ignored, and the type ofKin the transformed type should be the same as the type ofKinT.
Examples
Example 1:
type InputType = {
name: string;
age: number;
address: {
street: string;
city: string;
};
};
type Mapping = {
name: string;
age: number;
address: {
street: string;
city: string;
zip: string;
}
};
type TransformedType = TransformType<InputType, Mapping>;
// TransformedType should be:
// {
// name: string;
// age: number;
// address: {
// street: string;
// city: string;
// zip: string;
// };
// }
Explanation: The name and age properties are unchanged. The address property has a new zip property added.
Example 2:
type InputType = {
a: string;
b: number;
c: {
d: boolean;
};
};
type Mapping = {
a: number;
c: {
d: string;
e: boolean;
};
};
type TransformedType = TransformType<InputType, Mapping>;
// TransformedType should be:
// {
// a: number;
// b: number;
// c: {
// d: string;
// e: boolean;
// };
// }
Explanation: a is transformed to number. b remains a number. c has its d property transformed to string and a new e property of type boolean is added.
Example 3: (Edge Case - Primitive Type)
type InputType = string;
type Mapping = {
someProp: number;
};
type TransformedType = TransformType<InputType, Mapping>;
// TransformedType should be:
// string
Explanation: Since InputType is a primitive, the mapping is ignored.
Constraints
Tcan be any valid TypeScript type, including object types, primitive types (string, number, boolean, symbol, bigint), tuples, unions, and intersections.Mmust be an object type.- The transformation should be recursive, handling nested objects correctly.
- The type definitions should be valid TypeScript and compile without errors.
- The solution should be type-safe.
Notes
- Consider using conditional types and recursive type definitions to achieve the desired transformation.
- Pay close attention to how you handle properties that exist in
Tbut not inM, and vice versa. - Think about how to handle primitive types and tuples gracefully.
- The goal is to create a general-purpose type transformation utility.
- You can assume that
Mwill only contain object types as values. You don't need to handle cases whereM[K]is a primitive.