typescript: constraining allowed keys in an object with string literals

2022-06-15

 | 

~2 min read

 | 

242 words

I was working on a project recently where I wanted to check incoming data to see if it conformed to my allow list. To do this, I had an allow list of keys and would create a new object with only keys that matched the allow list, while tracking any others to see if they should be tossed.

Effectively, the code looked like:

type AllowList = "Foo" | "Bar"
const headers = {}
const disallowedKeys = []
for (const [key, value] of Object.entries(incoming)) {
  switch (key) {
    case "Foo":
    case "Bar":
      headers[key] = value
    default:
      disallowedKeys.push(key)
  }
}

The question is, how could you ensure that headers only receives keys of this type?

Two different ways - depending on whether you want the AllowList to be string literals or an enum:

With an enum, it might look like:

enum ALLOWED_ENUM {
  Authorization = "Authorization",
  Another = "Another",
}

const headers: Partial<Record<ALLOWED_ENUM, string>> = {}

Whereas with string literals it might look like

type ALLOWED_STRINGS = "Authorization" | "Another"
const headers: Partial<Record<ALLOWED_STRINGS, string>> = {}

Since our headers object is now typed, we get type checking on the rest of the function:

const disallowedKeys = []
for (const [key, value] of Object.entries(incoming)) {
  switch (key) {
    case "Authorization":
    case "Another":
      headers[key] = value
    case "Not listed":
    //   headers[key] = value // -- this would error
    default:
      disallowedKeys.push(key)
  }
}

Here’s a Typescript Playground demonstrating this.



Hi there and thanks for reading! My name's Stephen. I live in Chicago with my wife, Kate, and dog, Finn. Want more? See about and get in touch!