Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Runtime Types in TypeScript

Jan Tvrdík
December 01, 2021

Runtime Types in TypeScript

Jan Tvrdík

December 01, 2021
Tweet

More Decks by Jan Tvrdík

Other Decks in Technology

Transcript

  1. TypeScript vs. Java // TypeScript var s = value as

    string // Java var s = (String) value;
  2. const postJson = async (path: string, body: any) => {

    const response = await fetch(path, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(body), }) return await response.json() } const response = await postJson(...) response. // no code completion
  3. const postJson = async (path: string, body: any) => {

    const response = await fetch(path, { ... }) return await response.json() } type SignInResponse = { token: string } const response = await postJson(...) as SignInResponse response. // code completion works
  4. const postJson = async <T>(...): Promise<T> => { const response

    = await fetch(path, { ... }) return await response.json() } type SignInResponse = { token: string } const response = await postJson<SignInResponse>(...) response. // code completion works & it looks FANCY
  5. const postJson = async (...): Promise<unknown> => { const response

    = await fetch(path, { ... }) return await response.json() } type SignInResponse = { token: string } const response = await postJson(...) if ( typeof response === 'object' && response !== null && 'token' in response && typeof response.token === 'string' ) { ... }
  6. function hasOwnProperty< X extends {}, Y extends PropertyKey >(obj: X,

    prop: Y): obj is X & Record<Y, unknown> { return obj.hasOwnProperty(prop) }
  7. if ( typeof response === 'object' && response !== null

    && hasOwnProperty(response, 'token') && typeof response.token === 'string' ) { response. // code completion works }
  8. type SignInResponse = { token: string } if ( typeof

    response === 'object' && response !== null && hasOwnProperty(response, 'token') && typeof response.token === 'string' ) { response. // code completion works }
  9. Runtime Type Design type RunTimeType<T> = (input: unknown) => input

    is T const stringType = (input: unknown): input is string => { return typeof input === 'string' }
  10. Runtime Type Design type RunTimeType<T> = (input: unknown) => asserts

    input is T const stringType = (input: unknown): asserts input is string => { if (typeof input !== 'string') throw new Error() }
  11. Runtime Type Design type RunTimeType<T> = (input: unknown) => |

    { ok: true, value: T } | { ok: false, reason: string } const stringType = (input: unknown) => { return typeof input === 'string' ? { ok: true, value: input } : { ok: false, reason: "not a string" } }
  12. Runtime Type Design type RunTimeType<T> = (input: unknown) => T

    // or throw Error in case of failure const stringType = (input: unknown) => { if (typeof input !== 'string') throw new Error(...) return input }
  13. Minimal Implementation const string = (input: unknown) => { if

    (typeof input !== 'string') throw new Error() return input }
  14. Minimal Implementation const number = (input: unknown) => { if

    (typeof input !== 'number') throw new Error() return input }
  15. Minimal Implementation const literal = <V>(value: V): Type<V> => {

    return (input: unknown) => { if (input !== value) throw new Error() return value } }
  16. Minimal Implementation const array = <T>(inner: RunTimeType<T>) => { return

    (input: unknown): T[] => { if (!Array.isArray(input)) throw new Error() return input.map(v => inner(v)) } }
  17. Minimal Implementation const object = <T extends Record<string, RunTimeType<any>>>(inner: T)

    => { return (input: unknown): { [P in keyof T]: ReturnType<T[P]> } => { if (input === null || typeof input !== 'object') throw new Error() const out: { [P in keyof T]: ReturnType<T[P]> } = {} as any for (const k in inner) { out[k] = inner[k]((input as any)[k]) } return out } }
  18. Usage const ArticleType = object({ id: number, title: string, category:

    object({ id: number, name: string, }), tags: array(object({ id: number, label: string, })), })
  19. const postJson = async (...): Promise<unknown> => { const response

    = await fetch(path, { ... }) return await response.json() } const SignInResponse = object({ token: string }) const response = SignInResponse(await postJson(...)) response. // code completion works & it looks FANCY
  20. New Power Level const IsDivisibleBySeven = (input: unknown) => {

    const n = number(input) if (n % 7 !== 0) throw new Error() return n }