A key feature of TypeScript is its ability to define and work with object types. Object types allow developers to explicitly define the shape and structure of an object, including the properties it should have and their data types. This provides greater clarity and helps catch potential errors during development.
In TypeScript, you can define object types using the syntax type followed by the name of the type and an equal sign. Inside curly braces, you can specify the properties of the object along with their data types. For example:
type Person = {
name: string;
age: number;
};
Here, we have defined a type called Person that represents an object with a name property of type string and an age property of type number. Once this type is defined, we can use it to declare variables, function parameters, or as return types for functions.
In addition to defining properties, object types can also define optional and readonly properties. Optional properties are denoted by adding a question mark (?) after the property name, while readonly properties are denoted by adding the readonly keyword before the property name. This allows for more precise control over object mutations and immutability.
Table of Contents
- 1 What are Object Types in TypeScript?
- 2 Overview of Object Types in TypeScript
- 3 Defining Object Types
- 4 Object Type Inference
- 5 Accessing Object Properties
- 6 Optional Properties in Object Types
- 7 Readonly Properties in Object Types
- 8 Extending Object Types
- 9 Union Types with Object Types
- 10 Intersection Types with Object Types
- 11 Type Narrowing with Object Types
- 12 Type Casting with Object Types
- 13 Best Practices for Object Types in TypeScript
- 13.1 1. Use Explicit Types
- 13.2 2. Use Interfaces or Types for Complex Objects
- 13.3 3. Use Readonly Properties
- 13.4 4. Avoid Optional Properties
- 13.5 5. Use Index Signatures for Dynamic Properties
- 13.6 6. Prefer Object Literal Syntax for Static Properties
- 13.7 7. Consider Using Discriminated Unions
- 13.8 8. Avoid Overly Nested Object Types
- 13.9 9. Consider Using keyof and typeof
- 13.10 10. Document Your Object Types
- 14 FAQ:
- 14.0.1 What is TypeScript?
- 14.0.2 What are object types in TypeScript?
- 14.0.3 How do you define object types in TypeScript?
- 14.0.4 Can object types have optional properties?
- 14.0.5 Can object types have readonly properties?
- 14.0.6 Can object types have index signatures?
- 14.0.7 Can object types have methods?
What are Object Types in TypeScript?
In TypeScript, object types define the structure and behavior of objects. They specify the properties and their types that an object should have, as well as any methods or functions that can be called on the object.
Object types can be defined using the interface keyword, which allows you to create custom types by specifying the properties and their types that an object should have.
For example, consider the following interface definition:
interface Person {
firstName: string;
lastName: string;
age: number;
}
This Person
interface defines an object type that should have three properties: firstName
and lastName
of type string
, and age
of type number
.
Once an object type is defined, it can be used to annotate variables, parameters, or return types to enforce type safety.
function greet(person: Person): string {
return "Hello, " + person.firstName + " " + person.lastName;
}
const john: Person = {
firstName: "John",
lastName: "Doe",
age: 25
};
console.log(greet(john)); // Output: Hello, John Doe
In the example above, the greet
function takes an argument of type Person
and returns a string. When calling the function with the john
object, which matches the defined Person
interface, the code executes without errors.
Object types can also represent complex and nested structures by using nested interfaces or union types.
By providing a way to define object types with strict typing, TypeScript helps catch errors at compile time and enables better code maintenance and understanding.
Summary
Object types in TypeScript define the structure and behavior of objects. They can be defined using interfaces and are used to enforce type safety in variables, parameters, and return types. Object types enable better code maintenance and understanding by catching errors at compile time.
Overview of Object Types in TypeScript
TypeScript is a programming language that extends JavaScript by adding static types. One of the key features of TypeScript is its ability to work with object types. Object types allow developers to define and work with structured data in TypeScript.
Object Type Syntax
In TypeScript, object types are defined using the following syntax:
type ObjectName = { property1: type1, property2: type2, ... };
Here, ObjectName
is the name of the object type, and { property1: type1, property2: type2, ... }
represents the properties and their corresponding types.
Object Type Examples
Let’s see a few examples of object types in TypeScript:
type Person = { name: string, age: number };
Person
is an object type that represents a person.- It has two properties:
name
of typestring
andage
of typenumber
.
type Employee = { id: number, name: string, department: string };
Employee
is another object type that represents an employee.- It has three properties:
id
of typenumber
,name
of typestring
, anddepartment
of typestring
.
Accessing Object Properties
Once an object type is defined, you can create objects of that type and access their properties using dot notation:
const person: Person = { name: "John", age: 25 };
console.log(person.name); // Output: John
Nested Object Types
Object types can also be nested, allowing for more complex data structures:
type Address = { street: string, city: string, country: string };
type Person = { name: string, age: number, address: Address };
In this example, Person
has an additional property called address
, which is of type Address
. This allows for the representation of a person’s address with multiple properties.
Summary
Object types in TypeScript provide a way to define and work with structured data. They allow developers to specify the properties and their corresponding types for an object. Object types can also be nested, enabling the creation of more complex data structures.
Defining Object Types
An object is a type in TypeScript that represents a collection of key-value pairs, where the keys are strings and the values can be of any type. In TypeScript, we can define the structure and types of object using the following syntax:
Example:
type Person = {
name: string;
age: number;
isEmployed: boolean;
};
In the example above, we define a type Person
that represents an object with three properties: name
, age
, and isEmployed
. The name
property is of type string
, the age
property is of type number
, and the isEmployed
property is of type boolean
.
We can then use the Person
type to declare variables or function parameters that should be of that type:
const person: Person = {
name: "John Doe",
age: 25,
isEmployed: true
};
function greet(person: Person) {
console.log(`Hello, ${person.name}!`);
}
greet(person);
In the example above, we declare a variable person
of type Person
and assign it an object that matches the structure defined by the Person
type. We also define a function greet
that takes a parameter person
of type Person
and logs a greeting using the name
property of the parameter.
Defining object types allows us to enforce the structure and types of objects in our code, providing better type checking and preventing potential bugs.
Object Type Inference
When working with TypeScript, the compiler can often infer the type of an object based on the values assigned to its properties. This is known as object type inference. Object type inference helps reduce the amount of type information that needs to be explicitly specified, making code more concise and readable.
Object type inference is based on the values assigned to object properties during initialization. The compiler analyzes the provided values and determines the most specific type that fits all the values. This inferred type is then assigned to the object, allowing TypeScript to provide accurate type checking and intelligence for the object’s properties.
Here is an example that demonstrates object type inference:
const person = {
name: "John",
age: 30,
email: "[email protected]",
};
In this example, TypeScript infers that the type of the “person” object is:
Property | Type |
---|---|
name | string |
age | number |
string |
The inferred type is based on the values assigned to the properties: a string for “name”, a number for “age”, and a string for “email”.
Object type inference is not limited to simple objects with just a few properties. It can also infer the types of deeply nested objects, arrays, and even functions assigned to object properties.
However, it’s important to note that object type inference only happens during object initialization. If the object is modified later, the type inference will not take into account any changes made. In such cases, it is recommended to explicitly specify the types to ensure accurate type checking throughout the codebase.
Accessing Object Properties
Dot Notation
One way to access object properties in TypeScript is by using the dot notation. With the dot notation, you can access properties of an object directly by specifying the object name followed by a dot (.) and the property name.
“`typescript
// Define an object
const user = {
name: ‘John’,
age: 30,
email: ‘[email protected]’
};
// Access properties using dot notation
console.log(user.name); // Output: John
console.log(user.age); // Output: 30
console.log(user.email); // Output: [email protected]
“`
Bracket Notation
Another way to access object properties is by using the bracket notation. With the bracket notation, you can access properties of an object by specifying the object name followed by a pair of square brackets ([]), containing the property name as a string.
“`typescript
// Define an object
const user = {
name: ‘John’,
age: 30,
email: ‘[email protected]’
};
// Access properties using bracket notation
console.log(user[‘name’]); // Output: John
console.log(user[‘age’]); // Output: 30
console.log(user[’email’]); // Output: [email protected]
“`
Variable Property Name
With bracket notation, you can also use a variable holding the property name to access object properties dynamically.
“`typescript
// Define an object
const user = {
name: ‘John’,
age: 30,
email: ‘[email protected]’
};
// Variable holding the property name
const propertyName = ‘name’;
// Access property dynamically using bracket notation
console.log(user[propertyName]); // Output: John
“`
Nullish Coalescing Operator
In TypeScript, you can use the nullish coalescing operator (`??`) to provide a fallback value for accessing object properties, in case the property value is undefined or null.
“`typescript
// Define an object with undefined property value
const obj = {
prop: undefined
};
// Access property with fallback value using nullish coalescing operator
console.log(obj.prop ?? ‘Fallback’); // Output: Fallback
“`
Note that the nullish coalescing operator (`??`) only provides a fallback value for undefined or null values, but not for other falsy values such as an empty string (“”) or 0. If the property value is any falsy value other than undefined or null, the fallback value will not be used.
Optional Properties in Object Types
In TypeScript, object types can have optional properties. An optional property is marked with a question mark (?) after the property name in the object type definition. Optional properties can have a value or be undefined.
Here is an example of an object type with optional properties:
type User = {
name: string;
age?: number;
email?: string;
};
In the example above, the age
and email
properties are optional. This means that an object of type User
can have these properties, but they are not required.
Here is how you can use the User
type:
const user1: User = {
name: "John",
age: 30,
email: "[email protected]"
};
const user2: User = {
name: "Jane"
};
In the example above, user1
has all three properties defined, while user2
only has the name
property defined. Since the age
and email
properties are optional, it is valid to create an object without them.
Optional properties are useful when you want to define object types that have certain properties, but those properties are not required for all instances of the object.
When accessing an optional property, you should consider that it can be undefined. You can use optional chaining (?.
) or nullish coalescing (??
) operators to handle optional properties:
const userAge = user1.age; // type: number | undefined
const userEmail = user1.email ?? "No email provided"; // type: string
In the example above, userAge
has a type of number | undefined
because the age
property is optional. The userEmail
has a type of string
because the email
property is optional and the nullish coalescing operator provides a default value if it is undefined.
As you can see, optional properties in object types provide flexibility when defining the structure of an object, allowing certain properties to be specified or not depending on the usage context.
Readonly Properties in Object Types
In TypeScript, we can mark properties in an object type as readonly. This means that once the value of a readonly property is set, it cannot be changed.
We can define readonly properties using the readonly
keyword before the property name:
type Person = {
readonly name: string;
readonly age: number;
};
Once we create an object of type Person
, the name
and age
properties become readonly. Let’s see an example:
let person: Person = {
name: "John",
age: 30,
};
person.name = "Mike"; // Error: Cannot assign to 'name' because it is a read-only property.
person.age = 31; // Error: Cannot assign to 'age' because it is a read-only property.
In the example above, attempting to modify the values of the name
and age
properties will result in a compilation error.
Readonly properties are useful when we want to enforce immutability in our code, preventing accidental changes to certain properties.
It’s worth noting that readonly properties can only be assigned a value when they are defined or through a constructor. After that, their values cannot be modified.
Additionally, it’s important to mention that readonly is a compile-time feature of TypeScript. It assists developers in catching potential mistakes during development, but it does not provide runtime immutability.
For more complex scenarios, where we need runtime immutability, TypeScript provides utility types like Readonly
, DeepReadonly
, and ReadonlyArray
.
In conclusion, readonly properties in object types restrict modifications to their values, helping to enforce immutability and catching potential errors at compile time.
Extending Object Types
In TypeScript, object types can be extended to add additional properties or methods. This allows you to create new object types that inherit the properties and methods from existing object types.
To extend an object type, you can use the &
operator, also known as the intersection type operator. This operator combines multiple object types into a single object type that has all the properties and methods from each individual type.
Extending Object Types Example
Let’s consider a simple example where we have two object types: Person
and Employee
. Employee
is an extension of Person
and has an additional property called employeeId
.
type Person = {
name: string;
age: number;
};
type Employee = Person & {
employeeId: string;
};
const person: Person = {
name: "John",
age: 27,
};
const employee: Employee = {
name: "Jane",
age: 32,
employeeId: "12345",
};
In the above example, the Employee
type is created by using the &
operator to combine the properties of Person
and { employeeId: string }
. This means that an Employee
object must have all the properties of a Person
object, as well as the employeeId
property.
Using Extended Object Types
Once you have extended an object type, you can use it just like any other object type. You can create variables or function parameters with the extended type, and you can access all the properties and methods defined in the extended type.
function greet(person: Person) {
console.log(`Hello, ${person.name}!`);
}
greet(person); // Output: Hello, John!
greet(employee); // Output: Hello, Jane!
In the above example, the greet
function accepts a parameter of type Person
, which means it can accept both Person
objects and Employee
objects. This is possible because the Employee
type extends the Person
type. Inside the function, we can access the name
property of the parameter, regardless of whether it is of type Person
or Employee
.
Conclusion
Extending object types in TypeScript allows you to create new object types that inherit the properties and methods of existing object types. This can be useful when you want to add additional properties or methods to an existing object type, without modifying the original type definition.
By using the &
operator, you can combine multiple object types into a single type that has all the properties and methods from each individual type.
Union Types with Object Types
Introduction
In TypeScript, we have the ability to create union types, which allow a value to have more than one possible type. This can be useful when we want a variable or parameter to accept multiple types of values. In this article, we will explore how union types can be used with object types.
Union Types
In TypeScript, union types are created by using the pipe character (|) between two or more types. For example, we can define a variable that can have either a string or a number value:
let value: string | number;
With this union type, we can assign a string or a number to the variable value
:
value = "Hello"; // Valid
value = 42; // Valid
By using union types, we can make our code more flexible and handle different types of values in a single variable or parameter.
Union Types with Object Types
When working with objects, we can also use union types to define multiple possible object shapes. This can be useful when dealing with different types of objects that share some common properties.
For example, let’s say we have two different types of shapes: Circle
and Rectangle
:
type Circle = {
type: "circle";
radius: number;
};
type Rectangle = {
type: "rectangle";
width: number;
height: number;
};
We can now define a variable that can hold either a Circle
or a Rectangle
object:
let shape: Circle | Rectangle;
With this union type, we can assign a Circle
object or a Rectangle
object to the variable shape
:
shape = { type: "circle", radius: 5 }; // Valid
shape = { type: "rectangle", width: 10, height: 20 }; // Valid
This allows us to write more generic code that can handle different types of shapes without sacrificing type safety.
Type Discrimination
When using union types with object types, it is often necessary to determine the specific type of an object at runtime. This can be done by checking for a common property that exists only in one of the types. In our example, the type
property can be used as a discriminator:
if (shape.type === "circle") {
// Process circle
} else if (shape.type === "rectangle") {
// Process rectangle
}
By using type discrimination, we can safely access the specific properties of the object and perform the appropriate actions based on its type.
Conclusion
Union types with object types in TypeScript allow us to define variables and parameters that can accept multiple types of objects. This provides flexibility and type safety when working with different shapes of objects. By using type discrimination, we can handle objects of different types in a runtime-safe manner.
Intersection Types with Object Types
Intersection types are a powerful feature in TypeScript that allow you to combine multiple object types into a single type. This can be useful when you want to create a type that has all the properties and methods from two or more existing types.
To define an intersection type, you can use the `&` operator. Here’s an example:
type Person = {
name: string;
age: number;
};
type Employee = {
id: number;
department: string;
};
type EmployeeWithPerson = Employee & Person;
In this example, we have two object types `Person` and `Employee`. By using the `&` operator, we create a new type `EmployeeWithPerson` that combines the properties of both `Person` and `Employee`.
You can then use the `EmployeeWithPerson` type to define variables or function parameters:
const employee: EmployeeWithPerson = {
name: 'John Doe',
age: 30,
id: 12345,
department: 'IT',
};
function printEmployee(employee: EmployeeWithPerson) {
console.log(`Name: ${employee.name}`);
console.log(`Age: ${employee.age}`);
console.log(`ID: ${employee.id}`);
console.log(`Department: ${employee.department}`);
}
printEmployee(employee);
By using intersection types, you can ensure that the variable or function parameter has all the required properties and methods from both object types. This can help catch errors early and provide better type safety in your code.
Intersection types can also be useful when working with libraries or APIs that return complex object types. You can combine the types provided by the library with your own custom types to create a single type that has all the necessary properties and methods.
However, it’s worth noting that intersection types can result in large and complex types, especially when combining multiple object types. So it’s important to use them judiciously and consider the potential impact on code readability and maintainability.
In conclusion, intersection types with object types are a powerful feature in TypeScript that allow you to combine multiple object types into a single type. They can be useful when you need to create a type that has all the properties and methods from multiple existing types. However, it’s important to use them judiciously and consider their potential impact on code complexity.
Type Narrowing with Object Types
When working with TypeScript, it’s common to encounter situations where you need to narrow down the type of an object. This can be useful when you want to perform specific operations or access certain properties that are only available on a more specific type.
Type Guards
Type narrowing in TypeScript can be achieved using type guards. Type guards are conditions that allow the compiler to infer more specific types based on certain checks.
One common type guard for object types is the instanceof
operator. It checks whether an object is an instance of a specific class or constructor function. For example:
“`typescript
class Rectangle {
width: number;
height: number;
}
class Circle {
radius: number;
}
function calculateArea(shape: Rectangle | Circle) {
if (shape instanceof Rectangle) {
// narrow the type to Rectangle
console.log(shape.width * shape.height);
} else if (shape instanceof Circle) {
// narrow the type to Circle
console.log(Math.PI * shape.radius * shape.radius);
}
}
“`
In the above example, the calculateArea
function has a parameter shape
with a union type of Rectangle | Circle
. Inside the function, the instanceof
operator is used to determine the specific type of the shape
object, allowing different calculations to be performed based on the type.
Type Aliases and Discriminated Unions
Another approach to type narrowing with object types is to use type aliases and discriminated unions. A discriminated union is a type that utilizes a common property, known as a discriminant, to narrow down to a specific subtype.
For example:
“`typescript
type Shape =
| { kind: ‘rectangle’; width: number; height: number }
| { kind: ‘circle’; radius: number };
function calculateArea(shape: Shape) {
switch (shape.kind) {
case ‘rectangle’:
// narrow the type to rectangle
console.log(shape.width * shape.height);
break;
case ‘circle’:
// narrow the type to circle
console.log(Math.PI * shape.radius * shape.radius);
break;
}
}
“`
In this example, the Shape
type is a union of two object types: { kind: 'rectangle'; width: number; height: number }
and { kind: 'circle'; radius: number }
. The kind
property acts as the discriminant, allowing the compiler to narrow down the type inside the switch statement.
Conclusion
Type narrowing with object types in TypeScript is essential for writing more precise and type-safe code. By using type guards and discriminated unions, you can narrow down object types and access properties that are specific to a certain subtype.
Remember to use type narrowing sparingly and only when necessary, as overly complex type conditions can make your code harder to read and understand. Use clear and descriptive property names as discriminants to make your code more readable and maintainable.
Type Casting with Object Types
Type casting is a way to tell TypeScript that you believe a variable has a different type than what is inferred by the type checker. This is useful when you know more about the type of a value than TypeScript does.
Explicit Type Casting
To explicitly cast a variable to a specific type, you can use the type assertion syntax in TypeScript. The syntax is the variable name followed by the “as” keyword, followed by the desired type. For example:
const myVariable: unknown = "Hello, TypeScript!";
const myString: string = myVariable as string;
In the above example, we have a variable myVariable
with an unknown type. We use the type assertion syntax as string
to cast it to the string
type, and assign the result to myString
.
Type Casting with Object Types
When dealing with object types, you can cast an object to a more specific type or to a union of types. This can be helpful when working with data that has been dynamically typed, such as data received from an API.
For example, suppose you have an object of type Person
, but some of its properties are optional:
type Person = {
name: string;
age?: number;
address?: string;
};
If you know that a specific instance of this object has all the properties filled, you can cast it to a more specific type:
const person: Person = {
name: "John Doe",
age: 25,
address: "123 Main St"
};
const specificPerson: Required<Person> = person as Required<Person>;
The Required<Person>
utility type in TypeScript is used to make all properties of a type required. In this case, we cast the person
object to the Required<Person>
type using the type assertion syntax as Required<Person>
, and assign it to specificPerson
.
You can also cast an object to a union of types. For example, suppose you have an object that can be either of type Person
or Employee
:
type Employee = {
name: string;
age: number;
jobTitle: string;
};
const object: Person | Employee = { name: "Jane Smith", age: 30, jobTitle: "Software Engineer" };
const personOrEmployee: Person & Employee = object as Person & Employee;
In this case, we cast the object
to the type Person & Employee
, which is a union of the Person
and Employee
types. This means that the resulting object must have all the properties from both types.
Conclusion
Type casting with object types can be useful in situations where you have more information about the type of a variable than TypeScript does. By using the type assertion syntax, you can explicitly cast a variable to a specific type or a union of types.
Best Practices for Object Types in TypeScript
1. Use Explicit Types
When defining object types in TypeScript, it is best to use explicit types instead of relying on type inference. This makes the code more readable and helps prevent any unexpected behavior. Instead of writing:
const obj = { name: "John", age: 25 };
It is better to write:
const obj: { name: string, age: number } = { name: "John", age: 25 };
2. Use Interfaces or Types for Complex Objects
For complex objects with multiple properties, it is a good practice to use interfaces or types to define the structure of the object. This makes the code more maintainable and easier to understand. For example:
interface User {
name: string;
age: number;
address: string;
}
or
type User = {
name: string;
age: number;
address: string;
};
3. Use Readonly Properties
If a property of an object should not be modified after it is initialized, it is a good practice to use the “readonly” modifier. This helps prevent accidental modifications and improves code reliability. For example:
interface Person {
readonly name: string;
age: number;
}
4. Avoid Optional Properties
Optional properties in object types can sometimes lead to unexpected behavior or introduce conditions that need to be checked. It is generally better to define all required properties explicitly. If a property is truly optional, consider using union types or conditional types to define more precise constraints. For example:
interface Options {
timeout: number;
onError?: () => void;
}
can be rewritten as:
interface Options {
timeout: number;
onError: (() => void) | undefined;
}
5. Use Index Signatures for Dynamic Properties
If an object can have dynamic properties with unknown names, it is a good practice to use index signatures to define the type of these properties. This allows TypeScript to enforce type checking even for properties that are dynamically added. For example:
interface Dictionary {
[key: string]: number;
}
6. Prefer Object Literal Syntax for Static Properties
When defining object types with static properties, it is better to use object literal syntax instead of the “Record” utility type. This makes the code more concise and easier to read. For example:
interface Colors {
red: string;
green: string;
blue: string;
}
is preferred over:
type Colors = Record<"red" |="" "green"="" |="" "blue",="" string="">;"red">
7. Consider Using Discriminated Unions
If an object type can have different shapes or variants, consider using discriminated unions to define a more precise type. This allows TypeScript to perform exhaustive checks and improve type safety. For example:
interface Circle {
type: "circle";
radius: number;
}
interface Rectangle {
type: "rectangle";
width: number;
height: number;
}
type Shape = Circle | Rectangle;
8. Avoid Overly Nested Object Types
Avoid nesting object types too deeply, as it can make the code harder to read and understand. Consider decomposing complex object types into smaller, more readable types. This improves code maintainability and reduces the chances of introducing bugs. For example:
interface User {
id: string;
name: string;
address: {
street: string;
city: string;
country: string;
};
}
can be decomposed into:
interface Address {
street: string;
city: string;
country: string;
}
interface User {
id: string;
name: string;
address: Address;
}
9. Consider Using keyof and typeof
For cases where you need to extract the keys or the type of a given object, consider using the keyof
and typeof
operators. These operators provide a type-safe way to work with object types and reduce the chances of introducing errors. For example:
const obj = {
name: "John",
age: 25,
};
type Keys = keyof typeof obj; // "name" | "age"
10. Document Your Object Types
To ensure the maintainability of your codebase, it is important to document the expected structure of object types. Use comments or tools like TypeScript’s JSDoc annotations to provide clear explanations of the purpose and constraints of each object type.
FAQ:
What is TypeScript?
TypeScript is a statically-typed superset of JavaScript that adds optional static types to the language.
What are object types in TypeScript?
In TypeScript, object types refer to the types that describe the structure of an object, including its properties and methods.
How do you define object types in TypeScript?
In TypeScript, you can define object types using the syntax: `{ property: type }`. For example, `{ name: string, age: number }` defines an object type with `name` and `age` properties.
Can object types have optional properties?
Yes, object types can have optional properties. You can define optional properties by adding a question mark (?) after the property name. For example, `{ name?: string, age?: number }` defines an object type with optional `name` and `age` properties.
Can object types have readonly properties?
Yes, object types can have readonly properties. You can define readonly properties by adding the `readonly` modifier before the property name. For example, `{ readonly name: string, readonly age: number }` defines an object type with readonly `name` and `age` properties.
Can object types have index signatures?
Yes, object types can have index signatures. Index signatures allow you to define the types of properties that are not known in advance. You can define an index signature using the syntax: `[key: type]: valueType`. For example, `{ [key: string]: number }` defines an object type with string keys and number values.
Can object types have methods?
Yes, object types can have methods. You can define methods in object types using the syntax: `methodName(arg1: type1, arg2: type2): returnType`. For example, `{ getName(): string, setName(name: string): void }` defines an object type with `getName` and `setName` methods.