JSON in TypeScript: 5 Fatal Casting Mistakes and How to Fix Them

Online Python Trainer for Beginners

Learn Python easily without overwhelming theory. Solve practical tasks with automatic checking, get hints in Russian, and write code directly in your browser — no installation required.

Start Course

Introduction: The Myth of "Magical" Type Casting

\n\n

Every TypeScript developer sooner or later faces this situation: you receive JSON from the server, you're sure you know its structure, and you do a simple cast using as. And... nothing works. Class methods are missing, fields are undefined, and debugging turns into a quest.

\n\n

The question "How do I cast a JSON object to a TypeScript class?" is one of the most popular on Stack Overflow (tens of thousands of views). And it's no surprise. TypeScript is a superset that only exists at compile time. At runtime, your "class" is just a plain JavaScript object.

\n\n

In this article, we'll break down the 5 most common and dangerous mistakes developers make when working with JSON and TypeScript classes. You'll learn how to avoid them and get ready-made solutions for production code.

\n\n

Mistake #1: Using as for "Real" Casting

\n\n

The most common pitfall is thinking that the jsonData as MyClass construct creates a class instance.

\n\n

What actually happens?

\n

The as operator (or angle-bracket syntax) is a type assertion. It tells the compiler: "Trust me, I know the type." But in JavaScript, it doesn't perform any magic.

\n\n
class User {\n    name: string = '';\n    greet(): string {\n        return `Hello, ${this.name}!`;\n    }\n}\n\nconst rawData = JSON.parse('{"name": "Anna"}');\nconst user = rawData as User;\n\nconsole.log(user.name); // "Anna" — works\nconsole.log(user.greet()); // TypeError: user.greet is not a function\n
\n\n

Why? Because rawData is a regular object { name: 'Anna' }. It doesn't have the User.prototype prototype, and therefore no greet method.

\n\n

How to fix it?

\n

Always create a real class instance. The basic way is Object.assign:

\n\n
const user = Object.assign(new User(), rawData);\nconsole.log(user.greet()); // "Hello, Anna!"\n
\n\n

But this method has nuances, which we'll discuss further.

\n\n

Mistake #2: Ignoring Nested Objects and Arrays

\n\n

You happily use Object.assign, but your data contains nested structures. And then the nightmare begins.

\n\n
class Address {\n    city: string = '';\n    zip: string = '';\n}\n\nclass Company {\n    name: string = '';\n    address: Address = new Address();\n}\n\nconst data = JSON.parse('{"name": "TechCorp", "address": {"city": "Moscow", "zip": "101000"}}');\nconst company = Object.assign(new Company(), data);\n\nconsole.log(company.address.city); // "Moscow" — lucky?\nconsole.log(company.address instanceof Address); // false!\n
\n\n

The problem: address remains a plain object, not an instance of Address. If Address has methods or computed properties, they will be lost.

\n\n

Solution: Recursive Casting

\n

Write a factory function or use the class-transformer library:

\n\n
import { plainToClass } from 'class-transformer';\n\nconst company = plainToClass(Company, data);\nconsole.log(company.address instanceof Address); // true\n
\n\n

class-transformer recursively traverses all fields and creates correct class instances based on @Type decorators.

\n\n

Mistake #3: Losing Default Values

\n\n

TypeScript allows you to set default field values directly in the class. But when "casting" JSON, these values can be overwritten with undefined.

\n\n
class Config {\n    theme: string = 'light';\n    fontSize: number = 14;\n    language: string = 'en';\n}\n\nconst partialData = JSON.parse('{"theme": "dark"}');\nconst config = Object.assign(new Config(), partialData);\n\nconsole.l                

Blogs

Book Recommendations