typescripttypescript-genericstype-constraints

Constrain objec properties to be object with properties whose keys must be the same in TypeScript


I am creating a sectional form. Each section must have initial values, and a validation schema. To help the team develop in a consistent way, I want to make sure that each section looks like this:

interface Section {
  initialValues: { [key: string]: any };
  validationSchema: { [key: string]: Yup.AnySchema };
}

Is there a way to constrain initialValues and validationSchema so that they must contain the same keys?

I.e., this will work:

const section1: Section = {
  initialValues: {
    name: "",
    age: undefined // will be a number
  },
  validationSchema: {
    name: Yup.string().required(),
    age: Yup.number().required()
  }
}

Whereas this would break:

const section1: Section = {
  initialValues: {
    name: "",
    age: undefined // will be a number
  },
  validationSchema: {
    name: Yup.string().required(),
    height: Yup.number().required() // should error, as height does not exist on initialValues
    // error because we are missing the age property and its validation here
  }
}

Is there a way to accomplish such a constraint in typescript, without having to predefine the keys?


Solution

  • It is impossible, in this case, to make standalone type with negation. In order to make it work, in typescript, you usually need to use function arguments inference. See example:

    import Yup from 'yup'
    
    interface Section<T, U extends T> {
        initialValues: T;
        validationSchema: U;
    }
    
    const sectionBuilder = <T, U extends T>(section: Section<T, T & U>) => {
    
    }
    
    sectionBuilder({
        initialValues: { name: 'hello' },
        validationSchema: { name: 'john' } // ok
    })
    
    sectionBuilder({
        initialValues: { name: 'hello' },
        validationSchema: { age: 42 } // expected error
    })
    
    sectionBuilder({
        initialValues: { name: 'hello' },
        validationSchema: { name: 'john', age: 42 } // expected error
    })
    
    
    

    Playground

    Generic U in Section is an intersection of T and U or in other words, U is a set of properties which are common for initialValues and validationSchema