Search code examples
typescriptinterfacekey

How to enforce array entries to be keys in a record in TypeScript?


I want to define a TypeScript interface like this:

interface ObjectSchema {
  properties: Record<string, any>;
  required: Array<string>;
}

but with the added constraint that entries of required should be keys of properties. How do I go about this?

A validly typed object would be:

let schemaA = {
  properties: {
    foo: {},
    bar: {},
  },
  required: ["foo"]
}

A invalidly typed object would be:

let schemaA = {
  properties: {
    foo: {},
    bar: {},
  },
  required: ["baz"] // "baz" isn't a key in properties.
}

Solution

  • Two type variables must be used, one for the keys of properties, one for the subset of said keys for required.

    asObjectSchema is just a convenient function to exploit the inference, so we don't have to annotate the type variables.

    interface ObjectSchema<A extends string, B extends A> {
      properties: Record<A, any>
      required: Array<B>
    }
    
    const asObjectSchema = <A extends string, B extends A>(
      schema: ObjectSchema<A, B>
    ): ObjectSchema<A, B> => schema
    
    const schemaA = asObjectSchema({
      properties: {
        foo: {},
        bar: {},
      },
      required: ['foo'],
    })
    
    const schemaB = asObjectSchema({
      properties: {
        foo: {},
        bar: {},
      },
      required: ['baz'], // Type '"baz"' is not assignable to type '"foo" | "bar"'
    })
    

    Playground