Dot Notation - Nested Property Transformations
What is Dot Notation?
Dot Notation is a feature of QuickModel that allows you to specify transformations for nested properties directly in the parent decorator, using dot syntax (.).
Instead of decorating every nested model, you can specify the necessary transformations from the parent model using dot notation to access deep properties.
Basic Syntax
@Quick({
'nested.property': Type, // 1 level deep
'parent.child.value': BigInt, // 2 levels deep
'deep.nested.path.value': Date, // N levels deep
})
class Model extends QModel<IModel> {
// ...
}Complete Example
Use Case: E-Commerce Cart Item
// Backend interfaces
interface IProduct {
id: string;
name: string;
price: string; // Serialized BigInt
createdAt: string; // Serialized Date
}
interface ICartItem {
product: IProduct;
quantity: number;
addedAt: string; // Serialized Date
}
// Option 1: Without Dot Notation (cleaner)
@Quick({
price: BigInt,
createdAt: Date,
})
class Product extends QModel<IProduct> {
declare id: string;
declare price: bigint;
declare createdAt: Date;
}
@Quick({
product: Product, // Product already has its transformations
addedAt: Date,
})
class CartItem extends QModel<ICartItem> {
declare product: Product;
declare quantity: number;
declare addedAt: Date;
}
// Option 2: With Dot Notation (everything in one place)
@Quick({
product: Product,
'product.price': BigInt, // ← Dot notation
'product.createdAt': Date, // ← Dot notation
addedAt: Date,
})
class CartItem extends QModel<ICartItem> {
declare product: Product;
declare quantity: number;
declare addedAt: Date;
}When to Use Dot Notation
✅ Good Use Cases
1. When you don't control the nested class
// Using a third-party class without decorators
import { ExternalModel } from 'external-library';
@Quick({
model: ExternalModel,
'model.timestamp': Date,
'model.amount': BigInt,
})
class MyModel extends QModel<IMyModel> {
declare model: ExternalModel;
}2. For rapid prototyping
// Rapid development, without defining all classes
@Quick({
'address.zipCode': String,
'address.coordinates.lat': Number,
'address.coordinates.lng': Number,
})
class User extends QModel<IUser> {
address!: any;
}3. Context-specific transformations
// The same Address is used in different contexts
@Quick({
address: Address,
'address.deliveryDate': Date, // Only for orders, not for users
})
class Order extends QModel<IOrder> {
address!: Address;
}❌ When NOT to Use Dot Notation
1. When the nested class is reused heavily
// ❌ BAD: Repeating dot notation everywhere
@Quick({ product: Product, 'product.price': BigInt })
class CartItem extends QModel<ICartItem> {
/* ... */
}
@Quick({ product: Product, 'product.price': BigInt })
class OrderItem extends QModel<IOrderItem> {
/* ... */
}
@Quick({ product: Product, 'product.price': BigInt })
class WishlistItem extends QModel<IWishlistItem> {
/* ... */
}
// ✅ GOOD: Decorate Product once
@Quick({ price: BigInt })
class Product extends QModel<IProduct> {
price!: bigint;
}
@Quick({ product: Product })
class CartItem extends QModel<ICartItem> {
product!: Product;
}2. When logic is complex
// ❌ BAD: Dot notation for complex logic
@Quick({
'user.profile.settings.theme': (val) => validateTheme(val),
'user.profile.settings.language': (val) => normalizeLanguage(val),
'user.profile.settings.notifications.email': Boolean,
'user.profile.settings.notifications.push': Boolean,
})
class Account extends QModel<IAccount> {
/* ... */
}
// ✅ GOOD: Separate models with their own transformations
@Quick({
theme: (val) => validateTheme(val),
language: (val) => normalizeLanguage(val),
})
class Settings extends QModel<ISettings> {
/* ... */
}
@Quick({ settings: Settings })
class Profile extends QModel<IProfile> {
/* ... */
}
@Quick({ profile: Profile })
class User extends QModel<IUser> {
/* ... */
}Nesting Depth
Dot notation supports any depth level:
@Quick({
'level1.value': BigInt, // 1 level
'level1.level2.value': Date, // 2 levels
'level1.level2.level3.value': RegExp, // 3 levels
'level1.level2.level3.level4.value': Symbol, // 4 levels
// ... N levels supported
})
class DeepModel extends QModel<IDeepModel> {
level1!: any;
}Combining with Arrays
Dot notation works with arrays:
interface IUser {
posts: IPost[];
}
interface IPost {
id: string;
publishedAt: string;
metadata: [string, any][];
}
@Quick({
posts: [Post], // Array of Posts
'posts.publishedAt': Date, // Transforms publishedAt in each Post
'posts.metadata': Map, // Transforms metadata in each Post
})
class User extends QModel<IUser> {
posts!: Post[];
}Pros and Cons
✅ Pros
- Centralization: All transformations in one place
- Flexibility: No need to modify external classes
- Speed: Ideal for prototypes and agile development
- Context: Specific transformations for each usage
❌ Cons
- Duplication: If you reuse the model, you repeat the dot notation
- Maintainability: Harder to follow with many properties
- Verbose: More code than decorating the class directly
- Type Safety: TypeScript does not validate nested paths (strings)
Priority Rules
When combining dot notation with decorators in the nested model:
@Quick({ price: BigInt })
class Product extends QModel<IProduct> {
price!: bigint; // Transformation defined here
}
@Quick({
product: Product,
'product.price': String, // ← This transformation overrides Product's
})
class CartItem extends QModel<ICartItem> {
product!: Product;
}Rule: The closest decorator (parent) takes priority over the nested model's decorator.
Recommended Patterns
Pattern 1: Own Classes (Without Dot Notation)
// ✅ RECOMMENDED: For models you control
@Quick({ createdAt: Date })
class Address extends QModel<IAddress> {
createdAt!: Date;
}
@Quick({ address: Address })
class User extends QModel<IUser> {
address!: Address;
}Pattern 2: External Classes (With Dot Notation)
// ✅ RECOMMENDED: For classes you don't control
import { ThirdPartyModel } from 'external';
@Quick({
model: ThirdPartyModel,
'model.timestamp': Date,
'model.value': BigInt,
})
class MyModel extends QModel<IMyModel> {
model!: ThirdPartyModel;
}Pattern 3: Hybrid
// ✅ VALID: Mix as needed
@Quick({ createdAt: Date })
class Product extends QModel<IProduct> {
price!: bigint; // No default transformation
createdAt!: Date; // Always transformed
}
// In Cart: price is transformed to BigInt
@Quick({
items: [CartItem],
'items.product': Product,
'items.product.price': BigInt, // Context specific
})
class Cart extends QModel<ICart> {
items!: CartItem[];
}
// In Wishlist: price remains as number
@Quick({
items: [WishlistItem],
'items.product': Product,
// 'items.product.price' is NOT transformed here
})
class Wishlist extends QModel<IWishlist> {
items!: WishlistItem[];
}Summary
Use dot notation sparingly. For production code, prefer decorating each class. For prototypes or external classes, dot notation is perfect.