How to Declare Object Literals in TypeScript the Right Way

In this article, you will look at different ways to declare Object Literals in TypeScript

What are Object Literals?

They are a comma-separated list of name-value pairs. Think of JSON Objects. Below is an Object Literal which contains the information for a user.

An example of an Object Literal

Let’s look at different ways to declare object literals in TypeScript and some caveats associated with them.

1. Declaring an Object Literal with No Type

Declaring Object Literal with no Type Declaration

This is the most straightforward way and you declare the object in the same way as you would in javascript. However, this also means you are not using TypeScript as it is supposed to be used. 

In the first two statements, I tried to access the object’s members name and email and it worked fine. When I try to access a non-existing member number, TypeScript threw an error. I wasn’t able to add to any new members either.

2. Declaring an Object Literal with Type any

If you use type any, you do not get an error when you try to access a non-existing member and you can add new members to your object literal as well.

So this must be the correct way, right? No!

When you use the type any to declare, it is like a wild card. Although it would ‘fix’ the errors from the previous section, using any would make the type system kind of worthless. 

3. Declaring an Object Literal with Type object

Declaring an Object Literal with Type object

If you use the type object, your object literal is not going to work as you expect it to. In the first console statement, although I try to access a member of user, I got an error. Although typescript doesn't throw an error if you declare your object literal with the type error, it will not be able to find the members of the class when you try to access them. Similar to the previous case, you can add any new members either.

4. Using type Record<string, any>

Declaring Object Literal with type Record<string, any>

This case is similar to case #2 where we declared the object literal with type any. You will get undefined if you try to access a non-existing member and can add new members to your object. However, this is better than using a type of any. 

Declaring object literal with type Record<string, string>

Instead of using Record<string,any>, you could use Record<string,string> or Record<string, string | number>

In the above code snippet since I used Record<string,string>, TypeScript throws an error when I try to add a member which has a number value. This is useful when you do not know the structure of your object literal, eg: the response object from an API. 

5. Using a custom Type while declaration

Declaring Object Literal with Custom Type declaration

If you know the structure of your object literal, you can specify the type while you are declaring it. TypeScript will throw an error when you try to add new members or when you try to access non-existing members. Although this is a good use of typescript, it is better to use Interfaces to make your code look cleaner.

6. Using Interfaces

Declaring Object Literal with Interface

This case will show the same behavior as case #5 but it makes your code look much cleaner and you will be able to re-use the interface to declare multiple object literals. You can also extend from an existing interface, create unions of multiple interfaces, etc. 

In my interface, I also added age as an optional member therefore TypeScript doesn't throw an error although user1 has no member age but user2 does.

So what is the right way?

This is my opinion. 

In most cases, you should try to use interfaces wherever possible. This ensures that you are using TypeScript the way it is supposed to be and it also helps in type checking and autocomplete suggestions. You can have a separate file for all your type declarations and simply import the interfaces to separate the interfaces and your code logic.

However, in some cases when the object literal’s structure is too big or not known, I’d suggest using Record<string, any> or if you can be more specific, use Record<string, string|number> or something similar based on your use-case.

I would recommend not using type any or not declaring a type as shown in case #1 and case #2. This reduces the powers of TypeScript.

Conclusion

I hope you found this article helpful. Please let me know if you have any feedback or use different logic for declaring object literals. If you are interested, check out this article I wrote about moving from javascript to typescript