Real-world patterns
ShapeWire works best when each transform has one visible responsibility and the complete pipeline converts one transport model into one application model.
Choosing the right transform
| Need | Transform |
|---|---|
| Change transport field names | rename |
| Create an allowlisted model | pick |
| Remove a known set of fields | omit |
| Fill missing or nullish values | defaults |
| Convert field representations | normalize |
| Target one known nested object | at |
| Apply one transform to a collection | mapEach |
| Add or overwrite model metadata | merge |
| Run several transforms left to right | pipe |
User response with a nested profile
The transport response contains private data, snake-case names, and inconsistent nested values:
const rawUser = {
user_id: 42,
full_name: 'Ada Lovelace',
profile: {
created_at: '2026-07-01',
newsletter: 'yes',
timezone: null,
},
password_hash: 'private',
};
Transform the root and profile independently:
const toUser = pipe(
omit(['password_hash']),
rename({user_id: 'id', full_name: 'name'}),
at(
'profile',
pipe(
rename({created_at: 'createdAt'}),
defaults({timezone: 'UTC'}),
normalize({createdAt: 'isoDate', newsletter: 'boolean'}),
),
),
);
const user = toUser(rawUser);
The application model is:
{
id: 42,
name: 'Ada Lovelace',
profile: {
createdAt: '2026-07-01T00:00:00.000Z',
newsletter: true,
timezone: 'UTC',
},
}
Paginated product response
A product endpoint returns pagination and item fields in transport formats:
const rawPage = {
page_no: '2',
total_count: '48',
results: [
{
product_id: 'p-1',
product_name: 'Desk',
price: '129.50',
in_stock: 'yes',
internal_cost: '70',
},
{
product_id: 'p-2',
product_name: 'Lamp',
price: 'invalid',
in_stock: null,
internal_cost: '20',
},
],
};
Define the item transform once, then apply it at the renamed collection path:
const toProduct = pipe(
omit(['internal_cost']),
rename({
product_id: 'id',
product_name: 'name',
in_stock: 'inStock',
}),
defaults({inStock: false}),
normalize({price: 'number', inStock: 'boolean'}),
);
const toProductPage = pipe(
rename({page_no: 'page', total_count: 'total', results: 'items'}),
normalize({page: 'number', total: 'number'}),
at('items', mapEach(toProduct)),
);
const page = toProductPage(rawPage);
The final page model is:
{
page: 2,
total: 48,
items: [
{id: 'p-1', name: 'Desk', price: 129.5, inStock: true},
{id: 'p-2', name: 'Lamp', price: null, inStock: false},
],
}
Account model with application metadata
The final example removes transport-only fields, applies a default plan, converts a count, and adds fixed provenance metadata:
const rawAccount = {
account_id: 7,
display_name: 'Research',
plan_code: null,
member_count: '12',
debug: 'trace-123',
};
const toAccount = pipe(
omit(['debug']),
rename({
account_id: 'id',
display_name: 'name',
plan_code: 'plan',
member_count: 'memberCount',
}),
defaults({plan: 'free' as const}),
normalize({memberCount: 'number'}),
merge({source: 'crm' as const, selected: false as const}),
pick(['id', 'name', 'plan', 'memberCount', 'source', 'selected']),
);
const account = toAccount(rawAccount);
The resulting model is:
{
id: 7,
name: 'Research',
plan: 'free',
memberCount: 12,
source: 'crm',
selected: false,
}
These examples intentionally stop at data shaping. Fetching, validation, permissions, pricing decisions, localization, and rendering remain outside ShapeWire's documented scope.
Continue with the focused mapEach guide or merge guide for deeper collection and multi-source patterns.