Advanced Array & Object Transformations

Reviewed & published by Brayan K

Array and object transformations are techniques for reshaping data using methods like map, filter, and reduce to convert, combine, and restructure collections without mutating the originals.

Part of the free JavaScript course at LearnCodingFast — hands-on lessons with examples you run in your browser, plus practice exercises and a quick quiz.

💡 Running Code Locally: While this online editor runs real JavaScript, some advanced examples may have limitations. For the best experience:

Master data transformation pipelines for production-level applications

What You'll Learn in This Lesson

Why Transformations Matter

Transforming arrays and objects is at the core of modern JavaScript. Whether you're building a live dashboard, processing large datasets, building an API, or manipulating UI state, almost every real-world project relies heavily on mastering transformations.

Transforming Arrays with .map() — Beyond the Basics

Beginners use .map() for simple value changes. But the real power comes when you use .map() to restructure data objects completely.

const users = [
  { id: 1, name: "Ava", age: 19 },
  { id: 2, name: "Leo", age: 23 },
  { id: 3, name: "Mia", age: 17 }
];

const transformed = users.map(user => ({
  userId: user.id,
  fullName: user.name.toUpperCase(),
  isAdult: user.age >= 18
}));

console.log(transformed);

💡 Why this matters: You are not just modifying values — you are reshaping an entire structure, which is exactly what real applications need when sending responses from APIs or preparing data for UI components.

Filtering With Multiple Conditions

Instead of doing one long filter, use chained or combined conditions. This kind of filtering is used in dashboards, e-commerce apps, analytics tools, and any system where users need to sort or refine information.

const people = [
  { name: "Alex", age: 22, active: true },
  { name: "Sam", age: 17, active: false },
  { name: "Nora", age: 30, active: true }
];

const result = people.filter(
  p => p.age >= 18 && p.active === true
);

console.log(result);

Combining .map() + .filter() + .reduce()

Here's a real transformation pipeline that cleans data, restructures it, and calculates summary information — exactly like you'd do when building analytics.

const orders = [
  { id: 1, amount: 25, status: "completed" },
  { id: 2, amount: 50, status: "cancelled" },
  { id: 3, amount: 75, status: "completed" }
];

const summary = orders
  .filter(o => o.status === "completed")
  .map(o => o.amount)
  .reduce((sum, amt) => sum + amt, 0);

console.log("Total completed:", summary);

Worked Example: One Messy Payload, One Clean Report

Read this before writing your own pipeline. It is the full journey a real payload takes — normalise, filter, group, aggregate, present — in five steps that each do exactly one job. Every step returns something new, so the raw data is still raw at the end. Every non-obvious line is commented with what it produces.

// A full transformation pipeline: messy API rows in, a clean report out.
// Each step does one job and returns a NEW array or object, so the raw
// payload is never touched.

const raw = [
  { order_id: 101, cust: "ava", cat: "books", total: "24.50",  state: "paid" },
  { order_id: 102, cust: "leo", cat: "tech",  total: "199.00", state: "refunded" },
  { order_id: 103, cust: "ava", cat: "tech",  total: "45.25",  state: "paid" },
  { order_id: 104, cust: "mia", cat: "books", total: "12.00",  state: "paid" },
  { order_id: 105, cust: "leo", cat: "books", total: "8.75",   state: "pending" }
];

// STEP 1 — NORMALISE. Rename the API's snake_case keys to the names your app
// uses, and turn the "total" strings into real numbers: Number("24.50") -> 24.5
const normalised = raw.map(row => ({
  id: row.order_id,
  customer: row.cust,
  category: row.cat,
  total: Number(row.total),
  status: row.state
}));

console.log("normalised rows:", normalised.length);                  // 5
console.log("first total is a number?", typeof normalised[0].total); // number

// STEP 2 — FILTER. Keep only the rows this report is actually about.
const paid = normalised.filter(order => order.status === "paid");
console.log("paid rows:", paid.length);                              // 3

// STEP 3 — GROUP. reduce builds ONE object out of many rows. The accumulator
// "acc" starts as {} and gains a key per category as the rows stream past.
const byCategory = paid.reduce((acc, order) => {
  acc[order.category] ??= [];      // ??= fills the key in only if it is missing
  acc[order.category].push(order);
  return acc;                      // reduce must always return the accumulator
}, {});

console.log("categories found:", Object.keys(byCategory).join(", ")); // books, tech

// STEP 4 — AGGREGATE. Object.entries turns { books: [...], tech: [...] } into
// [["books", [...]], ["tech", [...]]], which you can map over like any array.
const totals = Object.entries(byCategory).map(([category, orders]) => ({
  category,
  count: orders.length,
  revenue: orders.reduce((sum, o) => sum + o.total, 0)
}));

// STEP 5 — PRESENT. Biggest first, then formatted as text.
// sort() changes the array it is called on, so sort a copy: [...totals]
const report = [...totals]
  .sort((a, b) => b.revenue - a.revenue)
  .map(t => t.category + " x" + t.count + " = " + t.revenue.toFixed(2))
  .join(" | ");

console.log(report);

// The raw payload never changed — every step returned something new.
console.log("raw still snake_case?", "order_id" in raw[0]);          // true

// ✅ Expected output:
// normalised rows: 5
// first total is a number? number
// paid rows: 3
// categories found: books, tech
// tech x1 = 45.25 | books x2 = 36.50
// raw still snake_case? true

🎯 Your Turn: Group and Total in One Pass

Step 3 above is the one people find hardest, so practise it on its own. The data and the printing are written for you — you write the three lines inside the reduce. Fill in each ___ and check against the expected output at the bottom.

// 🎯 YOUR TURN — total the hours per team in a single reduce
// Replace each ___ then press Run.

const tickets = [
  { id: 1, team: "web",    hours: 3 },
  { id: 2, team: "mobile", hours: 5 },
  { id: 3, team: "web",    hours: 2 },
  { id: 4, team: "data",   hours: 8 },
  { id: 5, team: "mobile", hours: 1 }
];

const hoursByTeam = tickets.reduce((acc, ticket) => {
  // 1) First time you meet a team, start its running total.
  acc[ticket.team] ??= ___;         // 👉 what number should a total start at?

  // 2) Add this ticket onto that team's total.
  acc[ticket.team] += ticket.___;   // 👉 which field holds the hours?

  // 3) Pass the accumulator on to the next row.
  return ___;                       // 👉 reduce must always return this
}, {});                             // {} is the starting accumulator

Object.entries(hoursByTeam).forEach(([team, hours]) => {
  console.log(team + ": " + hours + "h");
});

console.log("teams:", Object.keys(hoursByTeam).length);

// ✅ Expected output:
// web: 5h
// mobile: 6h
// data: 8h
// teams: 3

Advanced Object Transformations: Renaming & Restructuring

Restructuring objects is extremely common — especially when dealing with APIs that return messy or poorly structured data.

// Renaming keys
const product = {
  prod_id: 123,
  prod_name: "Keyboard",
  cost_usd: 55
};

const normalized = {
  id: product.prod_id,
  name: product.prod_name,
  price: product.cost_usd
};

console.log("Normalized:", normalized);

// Dynamic key transformation
const api = {
  product_id: 10,
  product_title: "Monitor",
  in_stock: true
};

const transformed = Object.fromEntries(
  Object.entries(api).map(([key, value]) => {
    const newKey = key.replace(/_/g, "");
    return [newKey, value];
  })
);

console.log("Transformed:", transformed);

Merging Objects: Deep vs Shallow

Merging objects incorrectly can break user settings or lose config fields. Understanding the difference is critical.

// ❌ Shallow merge overwrites nested objects
const base = { theme: { mode: "light", font: "Arial" } };
const update = { theme: { mode: "dark" } };

const shallow = { ...base, ...update };
console.log("Shallow (loses font):", shallow);

// ✅ Deep merge preserves all fields
function deepMerge(a, b) {
  const result = { ...a };
  for (const key in b) {
    if (typeof a[key] === "object" && typeof b[key] === "object") {
      result[key] = deepMerge(a[key], b[key]);
    } else {
      result[key] = b[key];
    }
  }
  return result;
}

const merged = deepMerge(base, update);
console.log("Deep merged:", merged);

Flattening Deeply Nested Arrays

Flattening is extremely useful when handling parsed JSON, scraped data, or API responses with unknown nesting levels.

// Simple flattening with .flat()
const nested = [1, [2, [3, [4]]]];
const flat = nested.flat(Infinity);
console.log("Flat:", flat);

// Advanced: Controlled depth flattening
function flattenDepth(arr, depth = 1) {
  if (depth === 0) return arr;
  return arr.reduce((acc, el) => {
    if (Array.isArray(el)) {
      return acc.concat(flattenDepth(el, depth - 1));
    } else {
      return acc.concat(el);
    }
  }, []);
}

console.log("Depth 1:", flattenDepth([1, [2, [3]]], 1));
console.log("Depth 2:", flattenDepth([1, [2, [3]]], 2));

Grouping Data — A Must-Have Skill

Grouping information is a common feature in dashboards, admin panels, and analytics tools.

Transforming Nested Objects & Arrays Together

Real-world datasets often mix arrays inside objects and objects inside arrays. This pattern is used in e-commerce, inventory management, and search/filter systems.

const catalogue = [
  {
    category: "Electronics",
    items: [
      { name: "Phone", price: 500, stock: 22 },
      { name: "Laptop", price: 1200, stock: 10 }
    ]
  },
  {
    category: "Home",
    items: [
      { name: "Vacuum", price: 150, stock: 5 },
      { name: "Mixer", price: 90, stock: 12 }
    ]
  }
];

// Flatten all items and add category
const allItems = catalogue.flatMap(c => 
  c.items.map(i => ({
    category: c.category,
    ...i
  }))
);

console.log("All items:", allItems);

Removing Keys Dynamically

Useful when you must sanitize user data or hide private fields before storing or displaying.

const user = {
  id: 1,
  name: "John",
  password: "secret123",
  token: "abc",
  email: "[email protected]"
};

const removeKeys = (obj, keysToRemove) =>
  Object.fromEntries(
    Object.entries(obj).filter(([k]) => !keysToRemove.includes(k))
  );

const clean = removeKeys(user, ["password", "token"]);
console.log("Sanitized user:", clean);

Converting Between Objects and Arrays

Converting between objects and arrays is essential for many data operations.

// Object → Array
const obj = { a: 1, b: 2, c: 3 };

const arr = Object.entries(obj).map(([k, v]) => ({ 
  key: k, 
  value: v 
}));

console.log("Object to Array:", arr);

// Array → Object
const arr2 = [
  ["name", "Adam"],
  ["age", 21]
];

const obj2 = Object.fromEntries(arr2);
console.log("Array to Object:", obj2);

Pivoting/Unpivoting Data (Spreadsheet-Style)

Very advanced technique used in data science and finance. This transformation converts wide tables into clean, analysis-ready formats.

const sales = [
  { month: "Jan", productA: 100, productB: 200 },
  { month: "Feb", productA: 80, productB: 150 }
];

// Unpivot / normalize
const normalized = sales.flatMap(row =>
  Object.entries(row)
    .filter(([k]) => k !== "month")
    .map(([product, amount]) => ({
      month: row.month,
      product,
      amount
    }))
);

console.log("Unpivoted:", normalized);

Object Normalization (Real API Technique)

APIs often return inconsistent or nested data. Before you can use it in a UI, you normalize it.

const apiUsers = [
  {
    uid: "u1",
    profile: { name: "Alice", age: 22 },
    roles: ["admin", "editor"]
  },
  {
    uid: "u2",
    profile: { name: "Mark", age: 31 },
    roles: ["viewer"]
  }
];

const normalized = apiUsers.map(u => ({
  id: u.uid,
  name: u.profile.name,
  age: u.profile.age,
  isAdmin: u.roles.includes("admin")
}));

console.log("Normalized:", normalized);

Advanced Data Filtering (Multi-Rule Engine)

Instead of writing 10 separate filters, build dynamic rule engines. This design is used in e-commerce filter systems, UI panels, admin dashboards.

const products = [
  { name: "A", price: 20, rating: 4.3, inStock: true },
  { name: "B", price: 55, rating: 3.9, inStock: false },
  { name: "C", price: 35, rating: 4.8, inStock: true }
];

const filters = {
  minPrice: 30,
  requireStock: true
};

const filtered = products.filter(p =>
  (filters.minPrice ? p.price >= filters.minPrice : true) &&
  (filters.requireStock ? p.inStock : true)
);

console.log("Filtered products:", filtered);

Aggregation Pipelines (Database Style)

A technique from MongoDB, SQL analytics, and finance dashboards. This is real-world data engineering.

const sales = [
  { category: "Food", amount: 10 },
  { category: "Tech", amount: 200 },
  { category: "Food", amount: 8 },
  { category: "Tech", amount: 150 }
];

// Sum revenue per category
const totals = sales.reduce((acc, s) => {
  acc[s.category] = (acc[s.category] || 0) + s.amount;
  return acc;
}, {});

console.log("Category totals:", totals);

// Combine scores by name
const raw = [
  { name: "Tom", score: 80 },
  { name: "Tom", score: 90 },
  { name: "Sarah", score: 100 }
];

const combined = raw.reduce((acc, entry) => {
  acc[entry.name] = acc[entry.name] || { 
    name: entry.name, 
    scores: [] 
  };
  acc[entry.name].scores.push(entry.score);
  return acc;
}, {});

console.log("Combined:", Object.values(combined));

Recursive Transformations (Real Hard Skill)

Handles unknown depth structures. This is used in menus, folder explorers, scene graphs in games, comment threads, and organization charts.

const tree = {
  id: 1,
  children: [
    {
      id: 2,
      children: [{ id: 3 }]
    },
    {
      id: 4
    }
  ]
};

function flattenTree(node, output = []) {
  output.push(node.id);
  if (node.children) {
    node.children.forEach(child => flattenTree(child, output));
  }
  return output;
}

console.log("Flattened tree:", flattenTree(tree));

Error-Tolerant Transformations (Production-Ready)

In production apps, data is often broken, missing fields, or inconsistent. This prevents app crashes.

// Cleaning potentially broken data
const users = [
  { id: 1, name: "Zara" },
  { id: null, name: "Tom" },
  { id: 3 }
];

const cleaned = users
  .filter(u => typeof u.id === "number" && u.name)
  .map(u => ({
    id: u.id,
    name: u.name.trim()
  }));

console.log("Cleaned:", cleaned);

// Safe transformation with fallbacks
function safeTransform(user) {
  try {
    return {
      id: user?.id ?? null,
      name: user?.name?.trim() ?? "Unknown",
      age: Number(user?.age) || 0
    };
  } catch (e) {
    return { id: null, name: "Unknown", age: 0 };
  }
}

console.log(safeTransform({ id: 10, name: "  Sam " }));
console.log(safeTransform(null));

Common Mistakes to Avoid

❌ Mutating Data During Transformations

// ❌ BAD: Mutates original array
const arr = [1, 2, 3];
arr.map((num, i) => arr[i] = num * 10);
console.log("Mutated original:", arr);

// ✅ GOOD: Creates new array
const arr2 = [1, 2, 3];
const newArr = arr2.map(num => num * 10);
console.log("Original unchanged:", arr2);
console.log("New array:", newArr);

// ❌ Using reduce for tasks map() can handle
const doubled = [1,2,3].reduce((acc, x) => {
  acc.push(x * 2);
  return acc;
}, []); // Overkill

// ✅ Use map when you just need transformation
const doubled2 = [1,2,3].map(x => x * 2);
console.log("Simple map:", doubled2);

Debugging Transformations

You MUST be able to break down pipelines to understand what's happening at each step.

// Method 1: Inspect each step
const result = [1, 2, 3, 4]
  .map(x => {
    console.log("After map:", x * 2);
    return x * 2;
  })
  .filter(x => {
    console.log("After filter:", x);
    return x > 4;
  });

console.log("Final result:", result);

// Method 2: Break into separate steps
const data = [1, 2, 3, 4];
const mapped = data.map(x => x * 2);
console.log("Mapped:", mapped);

const filtered = mapped.filter(x => x > 4);
console.log("Filtered:", filtered);

🎯 Mini-Challenge: Build a Stock Report

This is the whole lesson in one function: filter out the rows that do not count, derive a new field, group, aggregate, sort and format. Nothing is filled in — you get the rules and a fixed test drive. Match the expected output and your pipeline is correct.

// 🎯 MINI-CHALLENGE: build a stock report
//
// Write buildReport(items) from scratch. Only the brief is here.
//
// Given rows shaped { sku, category, qty, price }, it must:
// 1. Ignore any row whose qty is 0.
// 2. Give every remaining row a "value" of qty x price.
// 3. Group those rows by category.
// 4. Per category work out: how many rows, and the total value.
// 5. Sort the categories by total value, highest first.
// 6. Print one line per category in exactly this shape:
//        tools | rows=2 | 130.00
//    (category, then the row count, then the value to 2 decimal places)
// 7. Print a final line in exactly this shape:
//        TOTAL | 184.99
//
// buildReport must not modify "items" or the objects inside it.
// Hint: toFixed(2) turns 130 into the string "130.00".

// your code here


// --- Do not change the code below: this is the test drive ---
const items = [
  { sku: "A1", category: "tools",  qty: 4, price: 12.5 },
  { sku: "B2", category: "paint",  qty: 0, price: 30.0 },
  { sku: "C3", category: "tools",  qty: 2, price: 40.0 },
  { sku: "D4", category: "paint",  qty: 3, price: 15.0 },
  { sku: "E5", category: "safety", qty: 1, price: 9.99 }
];

buildReport(items);
console.log("rows still:", items.length);

// ✅ Expected output:
// tools | rows=2 | 130.00
// paint | rows=1 | 45.00
// safety | rows=1 | 9.99
// TOTAL | 184.99
// rows still: 5

Production Patterns to Remember

You Are Now Senior-Level in Transformations

If you truly understand all these patterns, you can:

📋 Quick Reference

MethodPurpose
.map()Transform each element into a new shape
.filter()Select elements matching conditions
.reduce()Aggregate/accumulate into a single value
.flatMap()Map then flatten one level
Object.entries()Convert object to [key, value] pairs
Object.fromEntries()Convert [key, value] pairs to object

Lesson Complete!

You've mastered advanced transformation patterns — map, filter, reduce, grouping, normalization, flattening, and production-safe pipelines. These skills directly power dashboards, APIs, and analytics systems.

Practice quiz

What does .map() return?

  • The original array, mutated
  • A single accumulated value
  • A new array of transformed elements
  • Only the elements that pass a test

Answer: A new array of transformed elements. .map() transforms each element into a new shape and returns a brand-new array.

Which method selects only the elements that match a condition?

  • .filter()
  • .map()
  • .reduce()
  • .flat()

Answer: .filter(). .filter() keeps only elements for which the callback returns true.

What does this pipeline produce: orders.filter(o => o.status === 'completed').map(o => o.amount).reduce((s, a) => s + a, 0) for amounts 25 (completed), 50 (cancelled), 75 (completed)?

  • 150
  • 75
  • 125
  • 100

Answer: 100. Only the completed amounts 25 and 75 survive the filter, and reduce sums them to 100.

Why is a shallow merge { ...base, ...update } risky for nested objects?

  • It is too slow
  • A nested object in update replaces the whole nested object, losing sibling fields
  • It mutates update
  • It throws on nested objects

Answer: A nested object in update replaces the whole nested object, losing sibling fields. Spread only copies the top level, so update.theme overwrites base.theme entirely and the font field is lost.

What does [1, [2, [3, [4]]]].flat(Infinity) return?

  • [1, 2, 3, 4]
  • [1, [2, [3, [4]]]]
  • [1, 2, [3, [4]]]
  • [4]

Answer: [1, 2, 3, 4]. flat(Infinity) flattens all nesting levels into [1, 2, 3, 4].

Object.entries({ a: 1, b: 2 }) returns what?

  • ['a', 'b']
  • [1, 2]
  • [['a', 1], ['b', 2]]
  • { a: 1, b: 2 }

Answer: [['a', 1], ['b', 2]]. Object.entries returns an array of [key, value] pairs.

Which method turns [key, value] pairs back into an object?

  • Object.keys()
  • Object.fromEntries()
  • Object.assign()
  • Object.values()

Answer: Object.fromEntries(). Object.fromEntries() builds an object from an iterable of [key, value] pairs.

What does .flatMap() do?

  • Flattens all levels
  • Filters then maps
  • Reduces to one value
  • Maps each element then flattens one level

Answer: Maps each element then flattens one level. .flatMap() maps each element and then flattens the result by a single level.

Why prefer .map() over a .reduce() that just pushes into an array?

  • map is the wrong tool here
  • map expresses the transformation intent more clearly
  • reduce cannot build arrays
  • map mutates the original

Answer: map expresses the transformation intent more clearly. Use .map() for transformation, .filter() for selection, .reduce() for aggregation — the clearest intent for each task.

What is the key mistake the lesson warns about during transformations?

  • Using arrow functions
  • Chaining too many methods
  • Mutating the original data instead of returning a new structure
  • Using const

Answer: Mutating the original data instead of returning a new structure. Functional transformations should produce new arrays/objects rather than mutating the originals, avoiding hidden side effects.

Continue this course