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 TypeDeclaring Object Literal with no Type Declaration
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 objectDeclaring 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 declarationDeclaring 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 InterfacesDeclaring 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.