DEV Community

Cover image for Intelligent JSON Objects in Typescript Using class-transformer
Blake Anderson
Blake Anderson

Posted on

Intelligent JSON Objects in Typescript Using class-transformer

In modern front-end development, functional programming has become the dominant paradigm. Frameworks like React and Vue encourage functional patterns to manage UI components, state, and interactions. However, there are situations where an object-oriented approach can be helpful, especially when managing complex data models or encapsulating logic in a more structured way.

This tutorial will show you how to introduce object-oriented programming (OOP) into your front-end projects using TypeScript and the class-transformer module. We’ll walk through how to transform API responses from plain JSON into fully-functional objects with methods and logic, making it easier to work with rich domain models.

Why Use class-transformer?

When you receive data from an API, it typically comes in the form of plain JSON objects. These objects carry the data but lack any of the methods or behaviors that you might want to associate with them in your TypeScript classes. For example, a task object fetched from a project management system would contain properties like id, name, and status, but it wouldn’t have any methods to modify that status or perform related operations.

The class-transformer library bridges this gap by converting plain JSON into class instances, allowing you to leverage object-oriented capabilities in your TypeScript code.

Getting Started with class-transformer

Let’s start by setting up a simple project that demonstrates how to use class-transformer to add object-oriented behavior to API responses.

Step 1: Install class-transformer

First, install class-transformer and its peer dependency reflect-metadata in your project.

npm install class-transformer reflect-metadata
Enter fullscreen mode Exit fullscreen mode

You’ll also need to enable some settings in your tsconfig.json to allow class-transformer to work correctly:

{
  "compilerOptions": {
    "experimentalDecorators": true,
    "emitDecoratorMetadata": true
  }
}
Enter fullscreen mode Exit fullscreen mode

Make sure you also import reflect-metadata at the top of your main entry file (e.g., index.ts or App.tsx).

import 'reflect-metadata';
Enter fullscreen mode Exit fullscreen mode

Step 2: Define Your TypeScript Classes

Let’s define a couple of classes to represent tasks in a project management application. We’ll start with a base Task class and a more specific DevelopmentTask class that extends the base class with additional properties and methods.

class Task {
  id: string;
  name: string;
  description: string;
  status: string;

  constructor(id: string, name: string, description: string, status: string) {
    this.id = id;
    this.name = name;
    this.description = description;
    this.status = status;
  }

  // Method to complete the task
  completeTask(): string {
    this.status = 'completed';
    return `${this.name} is completed.`;
  }
}

class DevelopmentTask extends Task {
  codeRepository: string;

  constructor(id: string, name: string, description: string, status: string, codeRepository: string) {
    super(id, name, description, status);
    this.codeRepository = codeRepository;
  }

  // Method to push code to a repository
  pushCodeToRepository(): string {
    return `Pushed code to ${this.codeRepository}.`;
  }

  // Override completeTask to add code repository logic
  completeTask(): string {
    this.pushCodeToRepository();
    this.status = 'completed';
    return `Development task '${this.name}' is completed. Code pushed to: ${this.codeRepository}`;
  }
}
Enter fullscreen mode Exit fullscreen mode

These classes encapsulate both the data (e.g., id, name, status) and the behavior (e.g., completeTask() and pushCodeToRepository()) of tasks in your project management system.

Step 3: Fetch API Data and Transform It into Class Instances

When you fetch data from an API, you typically get plain JSON responses. Here’s how we can use class-transformer to convert that JSON into fully-functional instances of our Task and DevelopmentTask classes.

import { plainToClass } from 'class-transformer';
import axios from 'axios';

// Example API response for a task
const response = await axios.get('/api/task');

// Transform the plain JSON into a DevelopmentTask instance
const task = plainToClass(DevelopmentTask, response.data);

console.log(task.completeTask()); // Outputs: Development task 'Build feature X' is completed. Code pushed to: github.com/repo
console.log(task.pushCodeToRepository()); // Outputs: Pushed code to github.com/repo.
Enter fullscreen mode Exit fullscreen mode

In this example, the JSON response from the API is automatically transformed into an instance of DevelopmentTask, giving you access to all the methods and behaviors defined in the class.

Step 4: Handling Nested Objects with @Type Decorator

In many cases, API responses may contain nested objects. For example, a Project might contain an array of Task objects. You can use the @Type decorator from class-transformer to ensure that nested objects are also properly transformed into class instances.

import { Type } from 'class-transformer';

class Project {
  id: string;
  name: string;

  @Type(() => Task) // Use the @Type decorator to transform nested objects
  tasks: Task[];

  constructor(id: string, name: string, tasks: Task[]) {
    this.id = id;
    this.name = name;
    this.tasks = tasks;
  }
}

// Example API response for a project
const projectResponse = {
  id: '1',
  name: 'Project X',
  tasks: [
    { id: '1', name: 'Task 1', description: 'Task description', status: 'in-progress' },
    { id: '2', name: 'Task 2', description: 'Task description', status: 'completed' },
  ],
};

// Transform the plain JSON into a Project instance, including the nested Task objects
const project = plainToClass(Project, projectResponse);

console.log(project.tasks[0].completeTask()); // Outputs: Task 1 is completed.
Enter fullscreen mode Exit fullscreen mode

In this case, the @Type decorator ensures that each task in the tasks array is transformed into an instance of the Task class, giving you full access to the class’s methods.

When to Use OOP in Front-End Development

Object-oriented programming can be a powerful tool in front-end applications, especially when working with complex data models or when encapsulating behavior directly within objects makes sense. Here are a few situations where OOP might be beneficial:

  • Encapsulating Business Logic: When working with domain models that require both data and behavior (e.g., tasks, users, orders), encapsulating that logic in classes can simplify your code and improve maintainability.

  • Handling Complex Data Structures: If your API responses contain nested objects or relationships between entities (e.g., a project containing tasks), OOP can make it easier to manage these relationships through classes and inheritance.

  • Improving Reusability and Extensibility: Classes allow you to reuse common logic through inheritance or composition. For example, a DevelopmentTask might inherit from a base Task class but add specific functionality for development-related work.

Conclusion

While functional programming is often the go-to paradigm in modern front-end development, there are still many scenarios where object-oriented programming can provide significant benefits. Using TypeScript’s class system, combined with the class-transformer module, allows you to transform plain JSON data into rich, behavior-packed objects. This approach lets you bring the power of OOP to the front end, helping you manage complex data models more effectively.

With the ability to transform API responses into intelligent objects, you can encapsulate both data and logic, making your code more modular, reusable, and easier to maintain. Whether you’re managing tasks, projects, or more intricate domain models, this approach gives you a flexible way to integrate OOP into your front-end applications when it’s most beneficial.

Top comments (0)