Search code examples
javascripttypescripttypescript-typingstypescript-generics

Return a typescript interface that changes based on function inputs


I have a typescript interface:

interface MyInterface {
    property1?: string;
    property2?: string;
};

type InterfaceKey = keyof MyInterface;

The code below creates an object based on MyInterface. There is a function called verifyObjectProperty that allows the user to pass in an InterfaceKey ('property1' or 'property2') as a second parameter.

The function validates that the object has a string value for the given key, so it can no longer be undefined.

// - Create an object based on the interface
const myObject: MyInterface = {
    property1: 'a string',
}

const verifyObjectProperty = (
    objectToVerify: MyInterface,
    properyToVerify: InterfaceKey
): MyInterface => {
    // - Make sure object has the desired property
    if (objectToVerify[properyToVerify] === undefined) {
        objectToVerify[properyToVerify] = 'a new string';
    }

    // - Return the object
    return myObject;
};

I want to make it so the verifyObjectProperty function returns a typescript interface that shows which string is guaranteed to be there.

const verifiedObject = verifyObjectProperty(myObject, 'property1');
type property1 = typeof verifiedObject['property1']; // string
type property2 = typeof verifiedObject['property2']; // string | undefined

Solution

  • Use ts conditional types and mapped types. These features allow you to create new types based on the properties of existing types.

    First, create a helper type that will take a property key and return a new type with that property guaranteed to be a string.

    type EnsureString<T, K extends keyof T> = T & { [P in K]: string };
    

    This type takes two parameters: T is the original type (MyInterface), and K is the key of the property you want to check as a string. It returns a new type that is the same as T, but with the property K guaranteed to be a string.

    Then, modify verifyObjectProperty to use this helper type.

    const verifyObjectProperty = <K extends keyof MyInterface>(
       objectToVerify: MyInterface,
       propertyToVerify: K
    ): EnsureString<MyInterface, K> => {
       // - Make sure object has the desired property
       if (objectToVerify[propertyToVerify] === undefined) {
           objectToVerify[propertyToVerify] = 'a new string';
       }
    
       // - Return the object
       return objectToVerify as EnsureString<MyInterface, K>;
    };
    

    After calling verifyObjectProperty, it will infer that the returned object has a string value for the specified property.

    const verifiedObject = verifyObjectProperty(myObject, 'property1');
    type property1 = typeof verifiedObject['property1']; // string
    type property2 = typeof verifiedObject['property2']; // string | undefined