Introduction: The Myth of "Magical" Type Casting
\n\nEvery 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.
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\nIn 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\nMistake #1: Using as for "Real" Casting
\n\nThe most common pitfall is thinking that the jsonData as MyClass construct creates a class instance.
What actually happens?
\nThe 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.
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\nWhy? Because rawData is a regular object { name: 'Anna' }. It doesn't have the User.prototype prototype, and therefore no greet method.
How to fix it?
\nAlways create a real class instance. The basic way is Object.assign:
const user = Object.assign(new User(), rawData);\nconsole.log(user.greet()); // "Hello, Anna!"\n\n\nBut this method has nuances, which we'll discuss further.
\n\nMistake #2: Ignoring Nested Objects and Arrays
\n\nYou happily use Object.assign, but your data contains nested structures. And then the nightmare begins.
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\nThe problem: address remains a plain object, not an instance of Address. If Address has methods or computed properties, they will be lost.
Solution: Recursive Casting
\nWrite a factory function or use the class-transformer library:
import { plainToClass } from 'class-transformer';\n\nconst company = plainToClass(Company, data);\nconsole.log(company.address instanceof Address); // true\n\n\nclass-transformer recursively traverses all fields and creates correct class instances based on @Type decorators.
Mistake #3: Losing Default Values
\n\nTypeScript allows you to set default field values directly in the class. But when "casting" JSON, these values can be overwritten with undefined.
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