I have a function called genWizardDefaultState which is used to generate a default state for a form. It's recursive so that it can support nested forms. I'm trying to make a recursive generic type so that the function actually returns a useful, typed output.
The function itself takes a JSON-like object whose values can only be Args, and returns an object of the same structure whose values are Fields. Args are defined like this:
interface TextArg {
fieldType: "text";
}
interface NumberArg {
fieldType: "number";
}
interface SelectArg {
fieldType: "select";
}
type Arg = TextArg | NumberArg | SelectArg;
And Fields are defined like this:
interface TextField {
value: string;
errors: string[];
}
interface NumberField {
value: number;
errors: string[];
}
interface SelectField {
value: any;
label: string;
errors: string[];
}
The more general Field type is generic: it takes an Arg and returns a specific Field based on the fieldType property:
interface ArgToFieldMap {
text: TextField;
number: NumberField;
select: SelectField;
boolean: BooleanField;
date: DateField;
}
type Field<T extends Arg> = ArgToFieldMap[T["fieldType"]];
This works for flat object inputs (as long as I use as const assertion on the input type, so that typescript actually infers fieldType to be the string literal instead of just string):
type FormFields<T extends Record<keyof T, Arg>> = {
[Key in keyof T]: Field<T[Key]>;
};
For example, this snippet does return the right type!
const exampleArgsObject = {
name: { fieldType: "text" },
age: { fieldType: "number" },
favoriteTowel: { fieldType: "select" },
} as const;
type formStateType = FormFields<typeof exampleArgsObject>;
But I'm hitting a wall when it comes to making this type recursive. I think I need to:
- Tell FormFields that the properties of its input type are either
Arg
orArray<FormField>
. - Use a conditional type as the the mapped output properties, to distinguish between
Arg
andArray<FormField>
input properties
This is what I've come up with so far:
type FormFieldsRecursive<T extends Record<keyof T, Arg | Array<FormFieldsRecursive<T>>>> = {
[Key in keyof T]: T[Key] extends Arg ? Field<T[Key]> : 'idk what goes here'
};
Any idea what I should be returning instead of "idk what goes here"? I know it should be some kind of generic type that returns an array of something, but I'm getting really confused. I'm starting to wonder if this is even possible!