How to type a es6 object/dictionary in Typescript

Viewed 119

For example, I have the below type and I'm using an es6 object to serve as a dictionary.

type User = {
    id: string,
    name: string   
}
const usersDict = { [key: string]: User  } = {}

But the problem is if I try to get a nonexistent user id, the above typing isn't correct. e.g.

const user = usersDict['user_id_that_doesnt_exist'] // type of user is User

Should I type usersDict as below instead?

const usersDict = { [key: string]: User | undefined } = {}

The problem for this one would be when looping through the values in the below example

const usersDict = { [key: string]: User | undefined } = {}
Object.values(usersDict).map((user)=>{
    // here the type of user is User|undefined, 
    // but it really should be just User type,
    // since it's impossible to be undefined
})
3 Answers

If you can use noUncheckedIndexAccess in your tsconfig then you'll get all the behavior you want:

type User = {
  id: string,
  name: string
}

type UserDictionary = {
  [userId: string]: User
}

const usersDict: UserDictionary = {};

const user = usersDict['user_id_that_doesnt_exist'] // type of user is User | undefined

Object.values(usersDict).map((user)=>{
    // here the type of user is User, as desired
})

" User | undefined " actually means you are saving keys with undefined as value, but according to your statement that is not true.

The issue is in understand what is a null check for, if you defined an object in a variable then you essentially give it two states (null || object) as null being the default state.

If your code always assigns the value to an object and since null is not an expected state, you will never put a null check for it or define null as expected value, otherwise it might not throw error where it should.

Object.values(usersDict) will never a undefined value unless you explicitly add an key with undefined value usersDict['user_id']= undefined;

You need to use [Partial<User>][1] for this case.

type User = {
  id: string,
  name: string
}

type Dictionary = Partial<Record<string, User>>

const usersDict: Dictionary = {}

const user = usersDict['user_id_that_doesnt_exist'] // type of user is User | undefined

type Values<T> = T[keyof T]

(Object.values(usersDict) as Array<NonNullable<Values<Dictionary>>>)
  .map((user /** User */) => { })

Playground

I have used type assertion as Array<...> because Dictionary should be typed as Partial and as far as I understood usersDict['user_id_that_doesnt_exist'] should return User | undefined

Related