Skip to content

Instantly share code, notes, and snippets.

@OliverJAsh
Last active July 29, 2023 18:16
Show Gist options
  • Save OliverJAsh/2a538639e35db183b0cc16ca8ab520a7 to your computer and use it in GitHub Desktop.
Save OliverJAsh/2a538639e35db183b0cc16ca8ab520a7 to your computer and use it in GitHub Desktop.
Records and dictionaries in TypeScript
/*
In JavaScript, objects can be used to serve various purposes.
To maximise our usage of the type system, we should assign different types to our objects depending
on the desired purpose.
In this blog post I will clarify two common purposes for objects known as records and dictionaries
(aka maps), and how they can both be used with regards to the type system.
*/
//
// # Dictionary/Map type
// - Keys are unknown, for example a dictionary of unknown user IDs (strings) to usernames.
// - All key lookups should be valid
// - Described using index signature types
// - Index signature keys can be strings or numbers. (JavaScript coerces numbers to strings at
// runtime.)
// - Optionally, index signature value can include `undefined` (i.e. to model keys that may not
// exist).
// - Also see JavaScript's built in `Map` type
//
{
// String index signature
const dictionary: { [userId: string]: string } = {
a: 'foo',
b: 'bar',
}
const a: string = dictionary.a
const b: string = dictionary['b']
const x: string = dictionary.x
const z: string = dictionary['z']
}
{
// String index signature including undefined (for increased safety)
const dictionary: { [userId: string]: string | undefined } = {
a: 'foo',
b: 'bar',
}
const a: string | undefined = dictionary.a
const b: string | undefined = dictionary['b']
const x: string | undefined = dictionary.x
const z: string | undefined = dictionary['z']
}
{
// Number index signature
const dictionary: { [userId: number]: string | undefined } = {
0: 'foo',
1: 'bar',
2: 'baz',
}
const a: string | undefined = dictionary[0]
const b: string | undefined = dictionary[100]
// Error: Element implicitly has an 'any' type because index expression is not of type 'number'.
const c: string | undefined = dictionary['200']
}
//
// # Record type
// - Keys are known, for example a record of known user IDs (`a` and `b`) and their usernames.
// - Unknown key lookups should be invalid
// - Described using interfaces (defined manually or via mapped types)
//
// Note: TypeScript handles unknown key lookups differently depending on the notation used:
// - For dot notation (e.g. `foo.bar`), TypeScript will always error that the unknown key does not
// exist.
// - For bracket notation (e.g. `foo['bar']`), TypeScript will fallback to using the index signature
// if there is one. If the type doesn't have an index signature, the type will be inferred as
// `any`. This means these errors will only be visible when the `noImplicitAny` compiler option is
// enabled, however it is possible to write a function to force TypeScript to only check the
// property types and not the index signature.
//
{
// Inferred type
const record = {
a: 'foo',
b: 'bar',
}
const a: string = record.a
const b: string = record['b']
// Error: Property 'x' does not exist on type '{ a: string; b: string; }'.
const x: string = record.x
// Error: Element implicitly has an 'any' type because type '{ a: string; b: string; }' has no
// index signature.
const z: string = record['z']
}
{
// Type annotation
const record: { a: string; b: string; } = {
a: 'foo',
b: 'bar',
}
const a: string = record.a
const b: string = record['b']
// Error: Property 'x' does not exist on type '{ a: string; b: string; }'.
const x: string = record.x
// Error: Element implicitly has an 'any' type because type '{ a: string; b: string; }' has no
// index signature.
const z: string = record['z']
}
{
// Mapped type type annotation
type UserId = 'a' | 'b';
const record: { [key in UserId]: string } = {
a: 'foo',
b: 'bar',
}
const a: string = record.a
const b: string = record['b']
// Error: Property 'x' does not exist on type '{ a: string; b: string; }'.
const x: string = record.x
// Error: Element implicitly has an 'any' type because type '{ b: string; a: string; }' has no
// index signature.
const z: string = record['z']
}
{
// Record helper type annotation
type UserId = 'a' | 'b';
const record: Record<UserId, string> = {
a: 'foo',
b: 'bar',
}
const a: string = record.a
const b: string = record['b']
// Error: Property 'x' does not exist on type 'Record<UserId, string>'.
const x: string = record.x
// Error: Element implicitly has an 'any' type because type 'Record<UserId, string>' has no
// index signature.
const z: string = record['z']
}
@MarcosMeli
Copy link

Thanks for the summary !!
you can use: { [key: string]: string } ...
instead of userId

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment