Transform Data with Functional Patterns
Practical Data Transformations pairs imperative vs. functional TypeScript for arrays, objects, normalization, grouping, and null-safe access.
Why it matters
Master everyday data transformations in TypeScript using practical functional programming patterns. This skill teaches you to efficiently reshape arrays, objects, and nested data, moving beyond verbose loops to more expressive and maintainable code.
Outcomes
What it gets done
Transform arrays using map, filter, and reduce.
Reshape objects by picking and omitting fields.
Safely access nested data structures.
Normalize API responses and group data effectively.
Install
Add it to your toolbox
Run in your project directory:
curl -fsSL https://spark.entire.vc/get/ag-fp-data-transforms | bash Overview
Practical Data Transformations
Compares imperative and functional TypeScript for everyday data work - array map/filter/reduce, object pick/omit/merge, API response normalization, groupBy/countBy/sumBy aggregation, and null-safe nested access via optional chaining or fp-ts Option. Use for TypeScript array/object transformations, API normalization, grouping/aggregation, or null-safe nested access, when deciding between native methods and fp-ts.
What it does
Practical Data Transformations covers everyday TypeScript data work - arrays, object reshaping, API response normalization, grouping, and null-safe nested access - showing the imperative approach first, then the functional equivalent, with an honest assessment of when each actually wins rather than defaulting to "functional is always better."
Array operations cover map (transform every element, e.g. cents to dollars), filter (keep matches via a named, reusable predicate), reduce (accumulate a value, with the honest note that a simple sum reads fine as a plain loop and the functional win shows up when reduction logic is complex or composed with other steps), and chaining (filter().filter().map().sort() as a readable pipeline of named predicates). It also covers the fp-ts Array module for cases native methods don't handle safely: A.head/A.lookup return Option instead of undefined, A.findFirst returns Option<T>, A.partition splits into two groups, A.takeLeft, and A.uniq.
Object transformations cover pick/omit utilities (versus one-off destructuring, which the guide says is perfectly fine for single uses), shallow merge via object spread ("defaults with overrides"), a one-level deepMerge helper for nested config objects, and immutable nested updates - full manual spread nesting versus a small recursive updatePath helper - with the honest note that for deeply nested state, a library like Immer or fp-ts lenses is usually worth it over hand-rolled spreading.
Data normalization walks through transforming a nested API response (orders containing embedded customer and product data) into a normalized { byId, allIds } state shape, comparing one big imperative loop against a set of small, independently testable extraction functions (extractCustomers, extractProducts, extractOrders) composed through a generic createNormalizedCollection helper - plus a separate example mapping snake_case API fields (user_id, first_name) to camelCase UI-ready fields with formatted dates and a default avatar fallback.
Grouping and aggregation cover generic groupBy, countBy, and sumBy utilities (parameterized by a key-extraction function, reused across order/status/revenue examples), fp-ts's NEA.groupBy for a result typed as Record<string, NonEmptyArray<T>>, and a multi-step invoice total calculation (line totals, subtotal, tax) built from small named functions.
Null-safe access contrasts nested if chains, native optional chaining (config.database?.connection?.host ?? 'localhost' - called out as the right default for simple access), and fp-ts Option for cases that need composition: a generic prop() accessor returning Option, chained O.flatMap traversal through a deeply optional config, safe array access via A.head/A.lookup plus O.map/O.getOrElse, safe record lookup via R.lookup, and combining multiple optional values with O.Do/O.bind plus O.alt fallback chains (e.g. full name, else nickname, else "Anonymous").
Four real-world examples tie the patterns together: transforming a raw API order into a UI-ready OrderSummary (formatted currency, formatted date, status label/color lookup), deep-merging typed nested AppSettings with a DeepPartial<T> user override, grouping orders by customer into per-customer spend summaries, and a config accessor pattern comparing plain optional chaining against an Option-based traversal for a deeply nested AppConfig.
The closing "when to use what" section is explicit: use native .map()/.filter()/.reduce() and optional chaining for simple, one-off, team-familiar transformations; reach for fp-ts when chaining operations that might fail, composing reusable pipelines, or when type-safe missing-value tracking matters; write custom utilities (groupBy, countBy, sumBy) for domain-specific operations repeated across the codebase; and for hot paths, note that chained .filter().map() creates intermediate arrays where a single reduce pass can be faster - but measure before optimizing, since the readability cost is often not worth it. A summary table maps each common task (transform, filter, accumulate, group, pick, merge, deep merge, null-safe access, normalize) to its recommended approach.
When to use - and when NOT to
Use this skill when transforming arrays, objects, grouped data, or nested values in TypeScript, for tasks involving reshaping API responses, null-safe access, aggregation, or normalization, or when practical functional patterns are wanted for everyday data work instead of low-level loops. Do not treat the output as a substitute for environment-specific validation, testing, or expert review, and stop to ask for clarification if required inputs, permissions, safety boundaries, or success criteria are missing.
Who it's for
TypeScript developers doing everyday data-shaping work - API normalization, grouping, safe nested access - who want to know when native array methods and optional chaining are enough versus when fp-ts's Option and Array modules genuinely pay off.
Source README
The imperative approach is acceptable when:
- The transformation is a one-off
- The logic is simple and linear
- Performance is critical and you've measured
- The team is more comfortable with it
FAQ
Common questions
Discussion
Questions & comments · 0
Sign In Sign in to leave a comment.