# Replace Array with Object

> Replace an array whose positions hold different kinds of data with an object that has a named field for each.

- kind: technique
- category: organizing-data
- canonical: https://aipatterns.dev/refactoring/techniques/replace-array-with-object
- inspired-by: https://refactoring.guru/replace-array-with-object

## Problem

An array (or tuple) carries unrelated values by position: `row[0]` is a SKU, `row[1]` a quantity, `row[2]` a warehouse code. Readers must remember the positions, and inserting a column shifts every index in the codebase.

## Solution

**Define a type with a named field for each position, convert the array at the point it is created, and read fields by name.**

## Before

```ts
// parsed from a CSV export: [sku, qty, warehouse, reorderPoint]
function lowStock(rows: [string, number, string, number][]): string[] {
  return rows
    .filter((r) => r[1] <= r[3])
    .map((r) => `${r[0]} at ${r[2]}: ${r[1]} left (reorder at ${r[3]})`);
}

function parseRow(line: string): [string, number, string, number] {
  const cols = line.split(",");
  return [cols[0], Number(cols[1]), cols[2], Number(cols[3])];
}
```

## After

```ts
interface StockLevel {
  sku: string;
  quantity: number;
  warehouse: string;
  reorderPoint: number;
}

function parseRow(line: string): StockLevel {
  const [sku, quantity, warehouse, reorderPoint] = line.split(",");
  return {
    sku,
    quantity: Number(quantity),
    warehouse,
    reorderPoint: Number(reorderPoint),
  };
}

function lowStock(levels: StockLevel[]): string[] {
  return levels
    .filter((s) => s.quantity <= s.reorderPoint)
    .map((s) => `${s.sku} at ${s.warehouse}: ${s.quantity} left (reorder at ${s.reorderPoint})`);
}
```

## Why

- Code states meaning (`s.reorderPoint`), not position (`r[3]`).
- Adding a column changes the parser and the type, not every consumer.
- The compiler catches misspelled fields; it cannot tell `r[1]` from `r[3]`.

## Steps

1. Define the type (interface, record or class) with a field per position.
2. At the place the array is created, build the object instead. If many consumers remain, temporarily keep both, or add a converter.
3. Change consumers one at a time from index access to field access. Run the tests after each.
4. Change function signatures from the array type to the new type.
5. Delete the converter and the tuple type when nothing uses them.

## Watch out

- Don't apply to arrays that hold many items of the same kind. A list of SKUs is a proper array.
- Short, local tuples are idiomatic in some places, such as `[key, value]` pairs from `Object.entries` or a React `useState` return. Destructure them into names instead of converting.
- Wire formats and CSV column order still matter at the boundary. Keep the positional mapping in exactly one parser.
- Some hot-path numeric code uses typed arrays for memory layout. Don't convert those without measuring.
- Inverse: none worth using; positional arrays are a regression except at serialization boundaries.

## Related

- Fixes [Primitive Obsession](https://aipatterns.dev/refactoring/smells/primitive-obsession.md) and [Data Clumps](https://aipatterns.dev/refactoring/smells/data-clumps.md).
- If behavior gathers around the new type, continue with [Extract Class](https://aipatterns.dev/refactoring/techniques/extract-class.md) or [Replace Data Value with Object](https://aipatterns.dev/refactoring/techniques/replace-data-value-with-object.md).

## Agent checklist

- [ ] Positions held different meanings, not a homogeneous list.
- [ ] The positional mapping lives in one parser or adapter.
- [ ] All index accesses were replaced with named fields.
- [ ] Signatures use the new type.
- [ ] Tests pass.
