Your Amazing Guide To Typescript Object

December 24, 2022
Temitope Asama
React.js

The Fastest Way to Build React UI

Convert Figma designs to production-ready React.js code. Build stunning apps and landing pages faster than your peers and competitors.

Introduction

Objects are an essential concept in programming, so important they constitute an entire programming model referred to as Object-Oriented Programming.

A variable, data structure, function, or method are all forms of objects in computer science. Objects have significance as memory locations and are referenced by identifiers called keys. Objects are, in reality, the basis of developing a program because they are the units of code that eventually emerge from the development process. An object is what the computer actually executes.

This article will discuss objects in Typescript, object types, and how to make the most of them.

However, before we proceed, I’d like to introduce you to a cool plugin for your React projects. Copycat cuts down your development time by almost 35% by converting your Figma files to up-and-running React projects! Not only that, but it also has support for your favorite tools like Typescript, Bootstrap, and TailwindCSS, amongst others! Check it out here!

Brief Introduction To TypeScript

TypeScript is an open-source programming language built and maintained by Microsoft.

According to the Stack Overflow Developer Survey 2022, it is the fifth most popular technology amongst software developers.

Typescript is a strict syntactical superset of JavaScript that extends the language with optional static typing. It transpiles to JavaScript and is intended for large-scale application development. Existing JavaScript programs are also valid in TypeScript because it is a superset of JavaScript.

Here are a few resources to get you familiar with Typescript if you want to learn more:

Roles played by Objects

In both JavaScript and Typescript, objects are capable of performing two roles (sometimes both concurrently, but always at least one of them):

Records: A predetermined number of properties that were known at the time of development. Each property can have a distinct type.

Dictionaries: A collection of properties whose names are unknown at the time of development. The property values and keys (strings and/or symbols) have the same type.

Creating Objects with Typescript

Objects are the primary means by which data is grouped and passed around in Typescript.

There are two different types of Typescript objects;

  • the Object type(uppercase “O”), which is the type for all instances of objects;
let firstObject: Object;

and,

  • object type (lowercase “O”), which is the type for all non-primitive values;
let secondObject: object;

Read here: The difference between object and Object in Typescript

Objects can also be grouped by their properties. As such, we would have;

  • Object Literal
// Object type literal
let thirdObject: {prop: string};

and,

  • Interface
// Interface
interface Person {
  prop: string;
}
let fourthObject: Person;

These are represented in TypeScript through object types, as we will see in the following sections.

Object Type Literals

In TypeScript, there are a few different ways to create objects. One of them is by employing the Object Literal Syntax. This syntax consists of a series of key-value pairs enclosed in curly braces and separated from one another by commas.

Consider the following example;

let Person = {
  firstName: "Allie",
  lastName: "Bob",
  age: 30,
}

Now that we know what an Object Literal is, let us identify different ways to write objects using this syntax.

Objects without Type

We can create a TypeScript object without assigning a type or using any type so as to opt out of type checking. A solid example of this would be our Person object above.

Objects with the Explicit “Any” Type

When we explicitly assign any type to a variable, the Typescript compiler does not perform type-checking on that variable.

Consider the example below;

let User:any;         // using Typescript's any type
 
//assign a object
User = {  id:1, name: "Anna" } 
 
console.log(User.name)  // Anna
 
//Attempting to access a non-existent property. No compiler error
console.log(User.price);     // no compiler error  
 
//You can assign a new object to the same variable
User = {firstName: "Anna", lastName: "Bob"}   // no compiler error  
 
//Attempting to access a non-existent property. No compiler error
console.log(item.name)   //ok

In our example above, we bypass type checking using Typescript’s any type, such that we may even access non-existent properties without getting an error.

We may also assign a new object to the item object without the compiler throwing any errors.

N.B: This method does not take full advantage of Typescript’s type-checking features and should be avoided.

Objects with the Implicitly Set “Any” Type

When we do not assign a type and TypeScript is unable to deduce its type from the assignment or use, the compiler assigns the type “any” to it. However, the noImplicitAny compiler option controls how the compiler acts. This may be turned on or off in the .tsconfig file like so.

{
  "compilerOptions": {
    "noImplicitAny":true   
  }
}

setting noImplicitAny to false

When set to false, the code operates just as it would if a type any were assigned directly. The variable is treated as being of type any by Typescript.

setting noImplicitAny to true

noImplicitAny prevents Typescript from treating a variable as any when it is set to true. Now, every time the “User” variable is used, it tries to infer the type.

let User;      // noImplicitAny is set to true
 
// assign an object
User = {  id:1, name: "Anna" } 
 
console.log(User.name)          // Anna 
 
User.firstName="";              // Compiler error 
console.log(User.firstName);    // Compiler error 
 
// But You can assign a new object to same variable
User = {firstName: "Anna", lastName: "Bob"} 
 
console.log(User.firstName)     // Anna
 
User.name="";                   // Compiler error 
console.log(User.name)          // Compiler error 

The User variable in the aforementioned example is assigned an object with the properties id and name. The compiler throws an error when we attempt to access the nonexistent firstName property.

However, we are still allowed to assign a new object to the item variable by the compiler. The Typescript compiler successfully identifies the new type and utilizes it to type-check the User variable’s further usage.

Typing Objects With Object Types

Typescript allows us to define types for objects and then annotate variables with that type. Instead of key-value pairs, like in untyped object literals, the object type consists of key-type pairs, which are separated by either a comma or a semicolon.

An example of an object type with three properties is shown below. The name property is of type string, the id property is of type number, and the login propertyis of type object.

{   
   id:number,      
   name:string,
   login: {
     username: string,
     password: string
   } 
}

Assigning Types To Objects

There are several methods for creating an object with a concrete type. They may be divided broadly into two groups. The first is the Anonymous Type, while the second is the Named Type.

Anonymous Object Types

Without utilizing a type or an interface, this is what happens when you declare an object for a certain item. A function parameter is one example of an anonymous object type. Consider a function that takes an object as an argument.

If you wish to make this object parameter’s object type anonymous, you must do so in the function definition. You define which properties the object should have. You specify the type of value for each property as well.

// Define a function with anonymous object type:
function myUserFunc(user: { username: string, bio: string }) {
  return `user: ${user.username}, bio: ${user.email}`
}

The above example defines an object type parameter, User. This parameter is an example of an anonymous object type, and it specifies that the object has two properties; username and bio. Both properties are required.

Anonymous types are best when you do not intend to re-use the type.

Named Object Types

In TypeScript, there are two methods of defining object types. The first one is Type Alias, while the second one is Interface.

In this instance, you choose one of these two to specify the object’s shape. You use the type alias or interface when you want to annotate an object with this shape. For all object properties, TypeScript will then utilize the type alias or interface to infer the types.

  • Type Alias

We may give an existing type a unique name by using Typescript’s Type Aliases feature. The keyword “type” comes first in the syntax, and then comes the name you want to give the new type. An assignment operator and the type literal are next in order.

  • Interfaces

Another way to create a Typescript object is via the Interface object type. Interfaces describe objects’ shapes as well as the specifications of each instance.

Another approach to naming an object type is through an interface declaration. The keyword interface comes first in the syntax, and then comes the name that you want to give the interface. The type literal then follows it.

// No.1: type alias
// Create a type alias for user object:
type User = {
  username: string;
  bio: string;
}

// No.2: interface
// Create am interface for user object:
interface User {
  username: string;
  bio: string;
}

// Use the type alias or interface to annotate user parameter:
function myFunc(user: User) {
  return `user: ${user.username}, email: ${user.bio}`
}

The object type itself has the same structure. There are still two string-type attributes. The distinction is that the object type is now specified independently or outside of the function or context in which it is utilized.

Reusability of Named and Anonymous Object types

The reusability of your code is a major advantage of using named types. When you specify object types as named, you may use them as many times as you wish. Additionally, you may use them wherever you choose if you export them. Write once and use it whenever and everywhere. This is not possible with Anonymous types.

// Define the type alias for interfaceUser object once:
type interfaceUser = {
    name: string;
    age: number;
}

// Use interfaceUser type alias for one function:
function getUser(user: interfaceUser) {
  return `name: ${user.name}, age: ${user.age}`
}

getUser({ name: 'Anne', age: 20 })
// Output:
// 'name: Anne, age: 20'

// Use interfaceUser type alias for another function:
function getUsers(users: interfaceUser[]) {
  const usersNames = users.map(user => user.name)

  return usersNames
}

getUsers([{
  name: 'Bob',
  age: 21
}, {
  name: 'Jane',
  age: 36
}, {
  name: 'Anne',
  age: 27
}])
// Output:
// [ 'Bob', 'Jane', 'Anne' ]

You cannot refer to an anonymous object type elsewhere in your code, as we did with our named type, since it has no name. It must be written again if you wish to reuse the shape it specifies. This is one of the reasons TypeScript programmers prefer named object types over anonymous ones. This does not, however, imply that you should never use an anonymous object type.

A decent rule of thumb is to consider the object and how likely it is that you will reuse its shape. It can be a good idea to establish a type alias or an interface if it is likely that you will work with its shape or anything similar. That way, you can refer to the alias or interface anytime you work with that particular shape.

Named types also make it much easier to make modifications as you work. Only the alias or the interface will need to be changed. Anywhere you use the alias or the interface after you make the adjustment will see it take effect. Compare this to updating all instances of that particular shape in your code by searching for them.

Named types will also assist you in minimizing the likelihood of bugs. If you need to edit any code to reflect the changed shape after updating the alias or interface, TypeScript will be able to alert you right away. Anonymous object types prevent this from happening since there is no one source of truth that TypeScript may rely on.

On the other hand, Anonymous types are very useful in cases where you will not be reusing the shape.

Object type and property modifiers

All attributes are required and mutable when you define an object type, whether anonymous or named. However, using property modifiers in TypeScript, you may change this.

Optional Properties

There is a distinction between an object that may possess certain properties and an object that must have certain properties. When you define an object type with certain attributes, TypeScript expects that the object you marked with that object type will include all the specified properties.

TypeScript will throw an error if you fail to declare any of these properties in the object. Additionally, TypeScript will only anticipate and accepts pre-defined and known properties. It will not expect or accept anything else. Therefore, if it discovers any additional properties other than the ones specified, it will also complain. Two options exist for escaping this.

The first method is to develop various versions of the object type to accommodate different use cases. This could work in some situations depending on how you change the object’s form. However, it would be ridiculous to create a new variety merely to make one attribute optional. Instead, you may inform TypeScript that you would be adding optional properties later on.

Additionally, this will inform TypeScript that the property may not always be specified, and it should complain if it is, in fact, not specified. Except if you try to utilize the property. Making a property optional may be achieved by adding the question mark symbol (?) immediately before the property name in the object type.

// Create object type with optional properties:
type Employee = {
  firstName: string;
  lastName: string;
  middleName?: string; // This property is optional (the '?' after the property name)
  yearOfEmployment?: number; // This property is optional (the '?' after the property name)
}

// This will work:
const user1: Employee = {
  firstName: "Jane",
  lastName: "Bob",
  middleName: "Alexa", 
  yearOfEmployment: 2018
}
// This will work:
const user2: Employee = {
  firstName: "Jane",
  lastName: "Bob",
  middleName: "Alexa",
  yearOfEmployment: 2018
}

// This will work:
const user3: Employee = {
  firstName: "Jane",
  lastName: "Bob",
}

// This will not work:
const user4: Employee = {
  firstName: "Jane"
  // The "lastName" property is required, but missing.
}

// TS error: Property 'lastName' is missing in type '{ name: string; }' but required in type 'Employee'.

The output of the code above would be;

Learn more about optional properties here: https://www.youtube.com/watch?v=pxkR0Mr3lqY

Readonly Properties

readonly is the second property modifier. This modifier enables you to define properties whose values must remain constant once they have been initialized. Keep in mind that this modifier only functions in TypeScript. In the event that you designate a property as readonly and then attempt to update it, TypeScript will throw an error.

This, however, won’t stop JavaScript from putting executing that change when it compiles. A readonly attribute for JavaScript does not exist, at least not right now. Put the readonly keyword right before the property in the object type to designate a property as read-only.

// Create object type with optional properties:
type Employee = {
  firstName: string;
  lastName: string;
  middleName: string;
  readonly yearOfEmployment: number; //make yearOfEmployment readonly
}

// This will work:
const user: Employee = {
  firstName: "Jane",
  lastName: "Bob",
  middleName: "Alexa",
  yearOfEmployment: 2018
}

// This will work:
// Try to change value of property "lastName" on "user" object:
user.lastName = 'Trainor'

// This will not work:
// Try to change value of readonly property "email" on "jack" object:
user.yearOfEmployment = '2020'
// TS error: Cannot assign to 'yearOfEmployment' because it is a read-only property

When we run the code above, we get an output like below;

Index signatures

You may find yourself in circumstances where you only know what kind of property to expect and what kind of value to expect. However, you might not be aware of the property’s exact name.

This is not a problem with TypeScript because of index signatures. You may indicate both the type of a property’s value and the type of the property using an index signature. This allows you a lot of flexibility because TypeScript will not raise any issues as long as both types are accurate.

You must keep in mind to utilize a slightly different syntax when declaring properties and methods while using index signatures. Property signatures define properties just like method signatures define methods.

So typically, you would first define a method or property that we will name an unknown “y,” add a colon and then add a type for the index property value.

This notifies TypeScript that the object contains the specified index property “y.” However, we are unaware of this “y.”

To circumvent this using an index signature, you must enclose the property in square brackets and add some type. This type specifies the type of the actual property. Number, symbol and string are the permitted types for index signatures. 

// Create object type with index signature:
type StringKey = {
  // The property will be a type of string:
  [key: string]: string;
}

// Create another object type with index signature:
type NumberKey = {
  // The property will be a type of number:
  [index: number]: string;
}

// This will work:
const user: StringKey = {
  // Property is always a string.
  firstName: 'Anna',
  lastName: 'Roberts',
}

// This will work:
const bookshelf: NumberKey = {
  // Property is always a number.
  1: 'Pride and Prejudice',
  2: 'Animal Farm',
}

// This will not work:
const languages: NumberKey = {
  // Properties are strings, not numbers.
  one: 'JavaScript',
  two: 'TypeScript',
}
// TS error: Type '{ one: string; two: string; }' is not assignable to type 'NumberKey'.
// Object literal may only specify known properties, and 'one' does not exist in type 'NumberKey'.

// This will also not work:
const pets: StringKey = {
  // Properties are strings,
  // but the values are numbers and not strings.
  puppy: 1,
  kitten: 2,
}
// TS error: Type 'number' is not assignable to type 'string'.

Learn more about index signatures here:

Conclusion

Objects are an integral part of programming. Typescript object types give us the flexibility of creating type-safe objects as well as using them easily in our applications.

So now that we have gone through all the amazing things you can do with typescript objects and typescript object types, I hope you employ them in your applications.

To learn more about Typescript, check out the Typescript official documentation here. At Copycat, we have a robust stash of front-end articles including Typescript and resources for you. They include;

Check it all out here!

Copycat is a fantastic tool for building UI components faster with support for tools like Typescript, TailwindCSS, Bootstrap, and more! It is your all-in-one plugin for converting your Figma files to up-and-running React code, saving up to 35% of development time! Get started today!

Related Articles

Convert Figma To React

Convert Figma designs to production-ready React.js code. Build stunning apps and landing pages faster than your peers and competitors.

Convert Design to Code