Skip to content

Item 74: Know How to Reconstruct Types at Runtime

要点

  • TypeScript types are erased before your code is run. You can't access them at runtime without additional tooling.
  • Know your options for runtime types: using a distinct runtime type system (such as Zod), generating TypeScript types from values (json-schema-to-typescript), and generating values from your TypeScript types (typescript-json-schema).
  • If you have another specification for your types (e.g., a schema), use that as the source of truth.
  • If you need to reference external TypeScript types, use typescript-json-schema or an equivalent.
  • Otherwise, weigh whether you prefer another build step or another system for specifying types.

正文

ts
interface CreateComment {
  postId: string
  title: string
  body: string
}

💻 playground


ts
app.post('/comment', (request, response) => {
  const { body } = request
  if (
    !body ||
    typeof body !== 'object' ||
    Object.keys(body).length !== 3 ||
    !('postId' in body) ||
    typeof body.postId !== 'string' ||
    !('title' in body) ||
    typeof body.title !== 'string' ||
    !('body' in body) ||
    typeof body.body !== 'string'
  ) {
    return response.status(400).send('Invalid request')
  }
  const comment = body as CreateComment
  // ... application validation and logic ...
  return response.status(200).send('ok')
})

💻 playground


ts
const val = { postId: '123', title: 'First', body: 'That is all' }
type ValType = typeof val
//   ^? type ValType = { postId: string; title: string; body: string; }

💻 playground


ts
import { z } from 'zod'

// runtime value for type validation
const createCommentSchema = z.object({
  postId: z.string(),
  title: z.string(),
  body: z.string(),
})

// static type
type CreateComment = z.infer<typeof createCommentSchema>
//   ^? type CreateComment = { postId: string; title: string; body: string; }

app.post('/comment', (request, response) => {
  const { body } = request
  try {
    const comment = createCommentSchema.parse(body)
    //    ^? const comment: { postId: string; title: string; body: string; }
    // ... application validation and logic ...
    return response.status(200).send('ok')
  } catch (e) {
    return response.status(400).send('Invalid request')
  }
})

💻 playground


ts
// api.ts
export interface CreateComment {
  postId: string
  title: string
  body: string
}

💻 playground


ts
import Ajv from 'ajv'

import apiSchema from './api.schema.json'
import { CreateComment } from './api'

const ajv = new Ajv()

app.post('/comment', (request, response) => {
  const { body } = request
  if (!ajv.validate(apiSchema.definitions.CreateComment, body)) {
    return response.status(400).send('Invalid request')
  }
  const comment = body as CreateComment
  // ... application validation and logic ...
  return response.status(200).send('ok')
})

💻 playground

Released under the MIT License.