Alias y Mapeo con @QAlias
Mapea propiedades entre el formato snake_case de tu API y camelCase en tu código TypeScript con el decorador @QAlias.
Problema
Las APIs REST suelen devolver JSON con claves en snake_case (ej: first_name, created_at), pero en TypeScript preferimos camelCase (firstName, createdAt). Con @QAlias haces este mapeo sin duplicar código.
Uso Básico
typescript
import { QModel, Quick, QAlias } from 'quickmodel';
// Interfaz del backend (snake_case)
interface IUserAPI {
id: number;
first_name: string;
last_name: string;
email_address: string;
created_at: string;
updated_at: string;
is_active: boolean;
}
@Quick({ created_at: Date, updated_at: Date })
class User extends QModel<IUserAPI> {
declare id: number;
@QAlias('first_name')
declare firstName: string; // ← camelCase en código
@QAlias('last_name')
declare lastName: string;
@QAlias('email_address')
declare emailAddress: string;
@QAlias('created_at')
declare createdAt: Date; // ← también transformado a Date
@QAlias('updated_at')
declare updatedAt: Date;
@QAlias('is_active')
declare isActive: boolean;
}
// Entrada: payload snake_case de la API
const apiResponse = {
id: 1,
first_name: 'María',
last_name: 'García',
email_address: 'maria@example.com',
created_at: '2026-01-10T10:00:00.000Z',
updated_at: '2026-01-10T15:30:00.000Z',
is_active: true,
};
const user = new User(apiResponse);
// Acceso camelCase en TypeScript ✅
console.log(user.firstName); // 'María'
console.log(user.lastName); // 'García'
console.log(user.isActive); // true
console.log(user.createdAt instanceof Date); // true
// Serializar de vuelta a snake_case ✅
const serialized = user.$qSerialize();
console.log(serialized.first_name); // 'María'
console.log(serialized.created_at); // '2026-01-10T10:00:00.000Z'
// ↑ Los alias se usan como claves de salidaRoundtrip Completo
@QAlias garantiza que el mismo payload entra y sale con las mismas claves:
typescript
// Crear desde payload de API
const user = User.create(apiResponse);
// Serializar
const plain = user.$qSerialize(); // devuelve el mismo shape con claves originales (snake_case)
// Recrear desde el objeto serializado (roundtrip)
const userCopy = User.create(plain);
console.log(userCopy.firstName === user.firstName); // true
console.log(userCopy.createdAt.getTime() === user.createdAt.getTime()); // trueModelo Anidado con Alias
Los alias también funcionan cuando el modelo es utilizado como campo anidado:
typescript
interface IAddressAPI {
street_name: string;
zip_code: string;
city_name: string;
country_code: string;
}
interface IOrderAPI {
order_id: string;
total_amount: number;
shipping_address: IAddressAPI;
placed_at: string;
}
@Quick()
class Address extends QModel<IAddressAPI> {
@QAlias('street_name')
declare streetName: string;
@QAlias('zip_code')
declare zipCode: string;
@QAlias('city_name')
declare cityName: string;
@QAlias('country_code')
declare countryCode: string;
}
@Quick({ placed_at: Date, shipping_address: Address })
class Order extends QModel<IOrderAPI> {
@QAlias('order_id')
declare orderId: string;
@QAlias('total_amount')
declare totalAmount: number;
@QAlias('shipping_address')
declare shippingAddress: Address;
@QAlias('placed_at')
declare placedAt: Date;
}
const order = Order.create({
order_id: 'ORD-001',
total_amount: 129.99,
shipping_address: {
street_name: 'Calle Mayor 5',
zip_code: '28001',
city_name: 'Madrid',
country_code: 'ES',
},
placed_at: '2026-02-15T09:30:00.000Z',
});
// Acceso camelCase ✅
console.log(order.orderId); // 'ORD-001'
console.log(order.shippingAddress.cityName); // 'Madrid'
console.log(order.shippingAddress.zipCode); // '28001'
console.log(order.placedAt instanceof Date); // true
// Serializado de vuelta a snake_case ✅
const plain = order.$qSerialize();
console.log(plain.order_id); // 'ORD-001'
console.log(plain.shipping_address.city_name); // 'Madrid'Alias con @QField para Formularios
Puedes combinar @QAlias con @QField para mantener el mapeo y el schema de formulario:
typescript
interface IProfileAPI {
first_name: string;
last_name: string;
birth_date: string;
}
@Quick({ birth_date: Date })
class ProfileForm extends QModel<IProfileAPI> {
@QAlias('first_name')
@QField({ widget: 'input', label: 'Nombre', required: true })
declare firstName: string;
@QAlias('last_name')
@QField({ widget: 'input', label: 'Apellido', required: true })
declare lastName: string;
@QAlias('birth_date')
@QField({ widget: 'datepicker', label: 'Fecha de nacimiento' })
declare birthDate: Date;
}
const form = ProfileForm.create({
first_name: 'Carlos',
last_name: 'López',
birth_date: '1990-05-20',
});
const schema = ProfileForm.getFormSchema();
console.log(schema[0].field); // 'firstName' (↑ nombre de la propiedad, no el alias)
console.log(form.firstName); // 'Carlos'
console.log(form.$qSerialize()); // { first_name: 'Carlos', last_name: 'López', ... }Combinar con @QRule
typescript
@Quick({ birth_date: Date })
class UserForm extends QModel<IProfileAPI> {
@QAlias('first_name')
@QRule({
predicate: (val: string) => val.trim().length >= 2,
message: 'El nombre debe tener al menos 2 caracteres',
})
declare firstName: string;
@QAlias('last_name')
declare lastName: string;
@QAlias('birth_date')
@QRule({
predicate: (val: Date) => val < new Date(),
message: 'La fecha de nacimiento no puede ser futura',
})
declare birthDate: Date;
}
const userForm = UserForm.create({
first_name: 'A',
last_name: 'García',
birth_date: '2030-01-01', // ❌ fecha futura
});
const result = userForm.$qCheckRules();
console.log(result.errors.map((e) => `${e.field}: ${e.message}`));
// ['firstName: El nombre debe tener al menos 2 caracteres',
// 'birthDate: La fecha de nacimiento no puede ser futura']Mejores Prácticas
typescript
// ✅ Declara la interfaz completa en snake_case (tal como llega de la API)
interface IUserAPI {
user_id: string;
display_name: string;
}
// ✅ Usa @QAlias para mapear a camelCase
class User extends QModel<IUserAPI> {
@QAlias('user_id')
declare userId: string;
@QAlias('display_name')
declare displayName: string;
}
// ✅ Usa User.create() que aplica strict typing contra IUserAPI
const user = User.create({ user_id: '1', display_name: 'Ana' });
// ❌ Evitar: No mezcles snake_case y camelCase en la interfaz
interface IMixed {
userId: string; // ← camelCase en interfaz de API es confuso
last_name: string;
}Próximos Pasos
- Campos Computados - Propiedades derivadas con
@QComputed - Validación - Combina con
@QRule