typescripttypescript-generics

Type error in generic FormData extraction


The function extract the form data and construct a Partial, the code works but i get this type error when using the function:

const loginForm = extractFormData<LoginForm>(formData);

Type 'LoginForm' does not satisfy the constrain 'StringOrNumber' , 'Index signature for type 'string' is missing in type 'LoginForm'

I dont understand why The type does not satisfy the constraint as LoginForm consist of strings.

I tried to make the login interface do string | number aswell and still the same error

interface LoginForm {
    username: string | number;
    password: string | number;
}

#CODE


interface LoginForm {
    username: string;
    password: string;
}

interface StringOrNumber {
    [key: string]: string;
}


const extractFormData = <T extends StringOrNumber>(formData: FormData): T => {
    //Use Partial<T> to construct the object incrementally
    const data: Partial<T> = {};

    formData.forEach((value, key) => {
        let valueType: string;
        let finalValue: string | number = value as string | number;

        if (typeof value === "number") {
            finalValue = value;
            valueType = "number";
        }

        else if (typeof value === "string") {
            valueType = "string";
        }

        else {
            throw new Error(`Unexpected value type for key "${key}": Expected string or number, but got ${typeof value}`);
        }

        //data[key as keyof T] = finalValue;
        data[key as keyof T] = finalValue as T[keyof T];
        // Optionally log the key and its type
        console.log(`${key}: ${finalValue} (${valueType})`);

        for (const key in data) {
            if (data[key] === undefined) {
                throw new Error(`Missing value for required field: ${key}`);
            }
        }
    });

    // Validate that all required fields are present and correctly typed
    for (const key in data) {
        if (data[key] === undefined) {
            throw new Error(`Missing value for required field: ${key}`);
        }
    }

    // Return the data object cast to the specified type T
    return data as T;
};

const loginForm = extractFormData<LoginForm>(formData);

Solution

  • A type that extends should be the same or wider than the extended one, but [key: string] means any string key which is wider than LoginForm's keys...

    You could use a mapped conditional type to verify that your generic parameter is an object containing only string key and values:

    Playground

    type StringOrNumber<T extends object> = {
        [K in keyof T as K extends string ? K : never]: T[K] extends string ? T[K]: never
    }
    
    const extractFormData = <T extends StringOrNumber<T>>(formData: FormData): T