Reputation: 617
I'm trying to make a data object for the state of a form with nested fields, but I'm having problems with type inference for nested fields. I've tried using unions and conditional types, but I don't fully understand how to do it correctly.
Link to TS Playground: https://tsplay.dev/mxoJzN
interface IField<V> {
value: V;
}
class Field<V> implements IField<V> {
value: V;
constructor(params: { value: V }) {
this.value = params.value;
}
}
type TFields<T> = { [K in keyof T]: T[K] extends IField<T[K]> ? IField<T[K]> : TFields<T[K]> }
class Form<T> {
fields: TFields<T>;
constructor(fields: TFields<T>) {
this.fields = fields;
}
}
/*
Expected:
{
id: IField<string>,
auth: {
login: IField<string>,
password: IField<number>,
code: {
isValid: IField<boolean>
}
}
}
Received:
{
id: string,
auth: TFields<{
login: string,
password: number,
code: TFields<{
isValid: boolean
}>
}>
}
*/
type FormSchema = {
id: string,
auth: {
login: string,
password: number,
code: {
isValid: boolean
}
}
}
const form = new Form<FormSchema>({
id: new Field({ value: '' }),
auth: {
login: new Field({ value: '' }),
password: new Field({ value: 0 }),
code: {
isValid: new Field({ value: false })
}
},
})
Upvotes: 1
Views: 384
Reputation: 15136
If you want to recursively apply TFields
for object properties, and use IField
for the other properties, you can define TFields
as
type TFields<T> = {
[K in keyof T]: T[K] extends object ? TFields<T[K]> : IField<T[K]>
}
This will make the form
sample correctly typed. If you have a specific set of primitive types, e.g. number
, string
, & boolean
, you could also replace the IField<T[K]>
above with T[K] extends number | string | boolean ? IField<T[K]> : never
, so you'll get an error for unsupported types.
Upvotes: 1