| name | data-transformation |
| description | WHAT: Transform API responses into domain models with transformers, helpers, and utilities. WHEN: converting API data shapes, implementing data pipelines, organizing transformation logic. KEYWORDS: transformer, helper, utils, API response, domain model, data shape, conversion, mapping. |
Data Transformation Patterns
Core Principles
Use transformer functions for API response shaping, helper functions for business logic, and utility functions for pure operations. Always organize these in separate files within feature directories.
Why this matters: Separation of concerns makes data transformation testable, maintainable, and reusable across the application.
When to Use This Skill
Use this skill when you need to:
- Transform API responses into application domain models
- Implement data conversion logic between different shapes
- Organize data transformation code in React Native applications
- Structure business logic and calculations
- Create testable transformation pipelines
File Organization Pattern
Organize data transformation files by feature:
features/
└── [feature-name]/
└── hooks/
└── [hook-name]/
├── index.ts # Public exports
├── types.ts # Type definitions
├── [hookName].ts # Main hook
├── transformers.ts # API transformations
├── helpers.ts # Business logic
└── utils.ts # Pure utilities
Key rule: Keep transformers, helpers, and utils in separate files for focused testing and clear boundaries.
Transformer Functions
Purpose
Transformers convert API responses into application domain models. They should:
- Only handle data shape conversion
- Not contain business logic
- Validate inputs and throw clear errors
- Use explicit types for inputs and outputs
Implementation Pattern
export const transformReactivationPriceResponse = (
response: ReactivationPriceResponse
): VoucherPriceInfo => {
const product = response.products[0];
if (!product) {
throw new Error('No product found in price response');
}
const calculatedValues = getCalculatedPriceValues(response, product);
return {
initialPrice: calculatedValues.productUnitPrice,
discountedPrice: calculatedValues.productPaidPrice,
currency: product.price.currency,
savings: calculatedValues.productUnitPrice - calculatedValues.productPaidPrice,
analyticsEventValues: {
productPriceId: product.priceId,
totalPrice: calculatedValues.totalPrice,
deliveryPrice: calculatedValues.deliveryPrice,
originalTotalPrice: calculatedValues.originalTotalPrice,
},
};
};
Common Transformer Patterns
export const transformUser = (apiUser: ApiUser): User => ({
id: apiUser.user_id,
name: `${apiUser.first_name} ${apiUser.last_name}`,
email: apiUser.email_address,
});
export const transformRecipeList = (apiRecipes: ApiRecipe[]): Recipe[] => {
return apiRecipes.map(transformRecipe);
};
export const transformOrder = (apiOrder: ApiOrder): Order => ({
id: apiOrder.order_id,
user: transformUser(apiOrder.user_data),
items: apiOrder.line_items.map(transformOrderItem),
total: convertMoneyToNumber(apiOrder.total_amount),
});
Helper Functions
Purpose
Helpers contain business logic and calculations. They should:
- Encapsulate business rules
- Be pure functions when possible
- Have clear single responsibilities
- Be easily testable in isolation
Implementation Pattern
export const getCalculatedPriceValues = (
response: ReactivationPriceResponse,
product: ReactivationPriceResponse['products'][0]
): GetCalculatedPriceValuesReturn => {
const productUnitPrice = convertMoneyToNumber(product.price);
const productPaidPrice = convertMoneyToNumber(product.paidPrice);
const deliveryPrice = convertMoneyToNumber(response.deliveryPrice);
const originalTotalPrice = convertMoneyToNumber(response.originalTotalPrice);
const totalPrice = convertMoneyToNumber(response.totalPrice);
return {
productUnitPrice,
productPaidPrice,
deliveryPrice,
originalTotalPrice,
totalPrice,
savings: productUnitPrice - productPaidPrice,
};
};
export const canAddToCart = (recipe: Recipe, userPlan: Plan, cartSize: number): boolean => {
if (!recipe.inStock) return false;
if (cartSize >= userPlan.maxItems) return false;
if (recipe.requiresPremium && !userPlan.isPremium) return false;
return true;
};
Utility Functions
Purpose
Utils are pure functions with no business logic dependencies. They should:
- Be completely generic
- Have no domain-specific knowledge
- Be reusable across features
- Have no side effects
Implementation Pattern
export const convertMoneyToNumber = (money: Money): number => {
return Math.round(parseFloat(money.amount) * 100);
};
export const formatPrice = (cents: number, currency: string): string => {
const amount = cents / 100;
return new Intl.NumberFormat('en-US', {
style: 'currency',
currency,
}).format(amount);
};
export const isValidEmail = (email: string): boolean => {
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
return emailRegex.test(email);
};
Type Definitions
Always define explicit types for transformation inputs and outputs:
export interface ReactivationPriceResponse {
products: Array<{
priceId: string;
price: Money;
paidPrice: Money;
}>;
deliveryPrice: Money;
originalTotalPrice: Money;
totalPrice: Money;
}
export interface VoucherPriceInfo {
initialPrice: number;
discountedPrice: number;
currency: string;
savings: number;
analyticsEventValues: {
productPriceId: string;
totalPrice: number;
deliveryPrice: number;
originalTotalPrice: number;
};
}
Why: Explicit types provide compile-time safety and serve as documentation.
Error Handling
Always validate transformer inputs and provide clear error messages:
export const transformReactivationPriceResponse = (
response: ReactivationPriceResponse
): VoucherPriceInfo => {
const product = response.products[0];
if (!product) {
throw new Error('No product found in price response');
}
if (!product.priceId || !product.price || !product.paidPrice) {
throw new Error('Invalid product data in response');
}
return {
};
};
Why: Early validation prevents downstream errors and provides clear error messages.
Testing Strategy
Test transformers in isolation:
describe('transformReactivationPriceResponse', () => {
it('transforms valid response correctly', () => {
const response: ReactivationPriceResponse = {
products: [
{
priceId: 'price-123',
price: { amount: '29.99', currency: 'USD' },
paidPrice: { amount: '19.99', currency: 'USD' },
},
],
deliveryPrice: { amount: '5.00', currency: 'USD' },
originalTotalPrice: { amount: '34.99', currency: 'USD' },
totalPrice: { amount: '24.99', currency: 'USD' },
};
const result = transformReactivationPriceResponse(response);
expect(result.initialPrice).toBe(2999);
expect(result.discountedPrice).toBe(1999);
expect(result.savings).toBe(1000);
});
it('throws error when no products', () => {
const response: ReactivationPriceResponse = {
products: [],
};
expect(() => transformReactivationPriceResponse(response)).toThrow(
'No product found in price response'
);
});
});
Test helpers separately:
describe('canAddToCart', () => {
it('returns true when all conditions are met', () => {
const recipe = { inStock: true, requiresPremium: false };
const plan = { maxItems: 5, isPremium: false };
expect(canAddToCart(recipe, plan, 3)).toBe(true);
});
it('returns false when out of stock', () => {
const recipe = { inStock: false, requiresPremium: false };
const plan = { maxItems: 5, isPremium: false };
expect(canAddToCart(recipe, plan, 3)).toBe(false);
});
});
Common Mistakes to Avoid
❌ Don't mix transformation with business logic:
export const transformUser = (apiUser: ApiUser): User => {
const user = {
id: apiUser.user_id,
name: `${apiUser.first_name} ${apiUser.last_name}`,
email: apiUser.email_address,
};
if (user.email.includes('@premium.com')) {
user.isPremium = true;
}
return user;
};
❌ Don't put everything in one file:
export const transform = (data) => { };
export const calculate = (data) => { };
export const format = (data) => { };
✅ Do separate concerns:
export const transformUser = (apiUser: ApiUser): User => ({
id: apiUser.user_id,
name: `${apiUser.first_name} ${apiUser.last_name}`,
email: apiUser.email_address,
});
export const isPremiumUser = (user: User): boolean => {
return user.email.includes('@premium.com');
};
export const formatEmail = (email: string): string => {
return email.toLowerCase().trim();
};
Documentation Guidelines
Add JSDoc comments for complex transformations:
export const transformReactivationPriceResponse = (
response: ReactivationPriceResponse
): VoucherPriceInfo => {
};
Quick Reference
Transformers: API shape → Domain model (no business logic)
Helpers: Business logic and calculations
Utils: Pure, generic functions
For detailed examples from production code, see references/examples.md.
For specific API documentation, see references/api-docs.md.