Question

How to Convert a JSON Object to a TypeScript Class

Question

I am reading a JSON object from a remote REST server. By design, the JSON contains all the properties expected by a TypeScript class. How can I cast the received JSON object to a typed variable?

I would prefer not to manually populate a TypeScript variable with a constructor that copies values from the JSON object, especially because the object is large and includes nested objects and many properties.

Short Answer

By the end of this page, you will understand the difference between a plain JSON object and a real TypeScript class instance. You will learn when a simple type assertion is enough, when it is misleading, and how to properly create class instances from JSON when you need class methods or runtime behavior.

Concept

TypeScript types exist mainly at compile time, not at runtime. That means when you receive JSON from a REST API, what you actually get is a plain JavaScript object, not an instance of your TypeScript class.

For example, if you define a class like this:

class User {
  name: string;

  getDisplayName() {
    return this.name.toUpperCase();
  }
}

and receive this JSON:

{ "name": "Ava" }

that JSON may have the same properties, but it is still not a User instance. It will not automatically have the class prototype, methods, getters, or constructor behavior.

This distinction matters because there are two different goals:

  • Tell TypeScript what shape the object has
  • Create a real class instance

A type assertion like this:

const user = json as User;

only tells the TypeScript compiler to treat the object as a User. It does not convert the object into a real User instance.

If your code only needs property access, this may be acceptable. If your code relies on class methods, validation logic, computed properties, or inheritance, you need to actually create an instance.

In real programming, this concept matters whenever you:

  • consume API responses
  • parse local storage data
  • deserialize configuration files
  • restore objects with methods after JSON parsing

The key idea is:

  • JSON gives you data
  • Classes give you behavior

Mental Model

Think of JSON as a cardboard cutout of an object, while a class instance is the real machine.

The cardboard cutout may look correct from the front:

  • same property names
  • same values
  • same nested structure

But it does not contain the moving parts:

  • methods
  • prototype chain
  • constructor setup
  • runtime logic

So if you only need to read the labels on the cardboard, a plain object is fine. But if you want to press buttons and use the machine's behavior, you need to build a real instance.

Take Quiz

Syntax and Examples

1. Type assertion for plain data

If you only need the object as structured data, you can use a type assertion:

class User {
  id!: number;
  name!: string;
}

const json = { id: 1, name: "Ava" };
const user = json as User;

console.log(user.name); // Ava

This works for property access in TypeScript, but user is still just a plain object.

2. Why methods do not work automatically

class User {
  id!: number;
  name!: string;

  greet() {
    return `Hello, ${this.name}`;
  }
}

const json = { id: 1, name: "Ava" };
const user = json as User;

console.log(user.name); // Ava
// console.log(user.greet()); // Runtime error: greet is not a function

Even though TypeScript allows the assertion, the actual object does not contain the class method.

3. Create a real class instance

If you need methods, create an instance and copy properties into it:

class User {
  id!: number;
  name!: string;

  greet() {
    return `Hello, ${this.name}`;
  }
}

const json = { id: 1, name: "Ava" };
const user = Object.assign(new User(), json);

console.log(user.greet()); // Hello, Ava

This gives you a real User instance with copied data.

4. Nested objects need extra handling

class Address {
  city!: string;
}

class User {
  name!: string;
  address!: Address;
}

const json = {
  name: "Ava",
  address: { city: "Paris" }
};

const user = Object.assign(new User(), json);

console.log(user.address.city); // Paris

This looks fine, but user.address is still a plain object, not an Address instance.

If Address has methods, you must convert that nested object too.

Step by Step Execution

Consider this example:

class Product {
  id!: number;
  title!: string;

  label() {
    return `#${this.id} ${this.title}`;
  }
}

const json = { id: 10, title: "Keyboard" };
const product = Object.assign(new Product(), json);

console.log(product.label());

Step by step:

  1. class Product defines a class with two properties and one method.
  2. json is a plain JavaScript object received from somewhere such as an API.
  3. new Product() creates a real class instance.
  4. Object.assign(new Product(), json) copies the enumerable properties from json into that instance.
  5. The result is an object that contains:
    • the copied data: id, title
    • the class behavior: label()
  6. product.label() works because product is a real Product instance.

Result:

#10 Keyboard

Now compare that with this:

const product = json as Product;
console.log(product.label());

TypeScript may allow it during compilation, but at runtime label does not exist on the plain object.

Real World Use Cases

This concept appears often in real applications:

API responses

A frontend fetches users, products, or orders from a REST API. The response is plain JSON. If the app only displays values, plain typed objects are often enough.

Domain models with behavior

If your class contains methods like:

  • getFullName()
  • isExpired()
  • calculateTotal()

then API JSON must be turned into real class instances before those methods can be used.

Local storage/session storage

When saving objects to storage, methods are lost because JSON only stores data. On restore, you may need to rebuild class instances.

Date handling

APIs often send dates as strings. Even if you assign the JSON to a class, a date field will still be a string unless you explicitly convert it.

Validation and business logic

Some projects keep logic inside classes. In those cases, deserialization is important so that runtime objects behave correctly.

Take Quiz

Real Codebase Usage

In real codebases, developers usually choose one of these patterns:

1. Use interfaces for API data

If the response is only data, many teams use an interface or type instead of a class.

interface UserDto {
  id: number;
  name: string;
}

This is often the simplest option because API responses are plain objects anyway.

2. Map DTOs to domain models

A common pattern is:

  • receive JSON as a DTO
  • validate or transform it
  • convert it to a class instance or richer model
interface UserDto {
  id: number;
  name: string;
}

class User {
  constructor(public id: number, public name: string) {}

  greet() {
    return `Hello, ${this.name}`;
  }
}

function toUser(dto: UserDto): User {
  return new User(dto.id, dto.name);
}

This is explicit and safe.

3. Use Object.assign for simple models

For lightweight classes without complex nested conversion:

const user = Object.assign(new User(), dto);

4. Add validation or guard clauses

Real projects often validate data before trusting it:

function isUser(value: any): value is { id: number; name: string } {
  return typeof value?.id === "number" && typeof value?.name === "string";
}

This helps prevent runtime bugs when APIs return unexpected data.

5. Convert nested structures explicitly

Developers often write factory functions for nested objects:

function toAddress(data: any): Address {
  return Object.assign(new Address(), data);
}

function toUser(data: any): User {
  return Object.assign(new User(), {
    ...data,
    address: toAddress(data.address)
  });
}

Common Mistakes

Mistake 1: Thinking as MyClass creates an instance

Broken example:

class User {
  name!: string;

  greet() {
    return `Hello ${this.name}`;
  }
}

const json = { name: "Ava" };
const user = json as User;

user.greet();

Problem:

  • user is still a plain object
  • greet() does not actually exist at runtime

Avoid it by creating an instance:

const user = Object.assign(new User(), json);

Mistake 2: Using classes when an interface is enough

If the object is only data, a class may add unnecessary complexity.

Better:

interface User {
  id: number;
  name: string;
}

const user: User = json;

Mistake 3: Forgetting nested conversion

Broken assumption:

const user = Object.assign(new User(), json);

This does not recursively convert nested objects into class instances.

Avoid it by converting nested objects separately.

Mistake 4: Trusting remote data blindly

API data may be missing fields or contain wrong types.

Broken example:

const user = json as User;
console.log(user.name.toUpperCase());

If name is missing or not a string, this can fail.

Avoid it with validation or type guards.

Mistake 5: Expecting JSON to preserve special values

JSON does not preserve:

  • methods
  • Date objects
  • Map
  • Set
  • class prototypes

These must be reconstructed manually if needed.

Comparisons

ApproachWhat it doesBest forLimitation
json as MyClassTells TypeScript to treat JSON as a typeSimple property accessDoes not create a real instance
const x: MyType = jsonAssigns plain data to a typed variableDTOs and API responsesNo class behavior
Object.assign(new MyClass(), json)Copies data into a real instanceSimple classes with methodsNested objects still need conversion
Manual mappingExplicitly transforms fieldsValidation and complex modelsMore code
Interface/type instead of classDescribes data shape onlyMost API response modelsNo methods or behavior

Class vs interface

FeatureClassInterface
Exists at runtimeYesNo
Can have methodsYesNo implementation
Good for API data shapeSometimesYes
Needs instantiationYesNo
Works naturally with JSONNot directlyYes
Take Quiz

Cheat Sheet

// Plain JSON object from API
const json = await response.json();
// Type assertion: compile-time only
const user = json as User;
// Real instance for flat objects
const user = Object.assign(new User(), json);
// Nested conversion pattern
const user = Object.assign(new User(), {
  ...json,
  address: Object.assign(new Address(), json.address)
});

Key rules:

  • as MyClass does not instantiate a class
  • JSON from APIs is a plain object
  • Use an interface or type if you only need data
  • Use Object.assign(new MyClass(), data) if you need class methods
  • Convert nested objects manually if they also need class behavior
  • Validate remote data when correctness matters

Common choice:

  • Data only -> interface/type
  • Data + methods -> class instance conversion

FAQ

Can I cast JSON directly to a TypeScript class?

You can use a type assertion, but it only affects TypeScript's static checking. It does not create a real class instance at runtime.

Why does as MyClass not give me class methods?

Because the original object is still a plain JavaScript object. It does not inherit from your class prototype.

What is the simplest way to convert JSON to a class instance?

For simple flat objects, use:

const obj = Object.assign(new MyClass(), json);

Should I use a class or an interface for API responses?

If the response is just data, an interface or type is usually the better choice. Use a class when you need methods or domain behavior.

Does Object.assign handle nested objects automatically?

No. It only copies top-level properties. Nested objects remain plain objects unless you convert them too.

What about arrays of objects?

Map each item:

const users = jsonArray.map(item => Object.assign(new User(), item));

Is manual mapping ever better?

Yes. It is safer when you need validation, transformation, renaming, or nested conversion.

Take Quiz

Mini Project

Description

Build a small example that loads plain API-style user data and converts it into real TypeScript class instances. This project demonstrates the difference between plain objects and class instances, including how to handle nested objects and how to use class methods safely.

Goal

Create a user model with a nested address model, convert JSON data into class instances, and call methods on the resulting objects.

Requirements

  1. Create an Address class with a method that returns a formatted location string.
  2. Create a User class with a method that returns a readable profile string.
  3. Start with plain JSON data that includes a nested address object.
  4. Convert the JSON into real User and Address instances.
  5. Print output using class methods to prove the conversion worked.
Take Quiz

Keep learning

Related questions

View more