# Replace Type Code with Class

> Replace a set of string or number codes that don't affect behavior with a dedicated type, so only valid codes can be used.

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

## Problem

A field holds one of a fixed set of codes, such as `"low" | "normal" | "urgent"` stored as a plain `string`, or `1`, `2`, `3` as `number`. Any string compiles, typos slip through, and facts about each code (its label, its sort order) are scattered across the codebase.

## Solution

**Create a type whose only instances are the valid codes, and move the data attached to each code into it.** Use this variant when the code does not drive conditional behavior; if it does, see the subclass or State/Strategy variants.

## Before

```ts
class Ticket {
  constructor(public title: string, public priority: string) {}
}

function sortQueue(tickets: Ticket[]): Ticket[] {
  const rank: Record<string, number> = { urgent: 0, normal: 1, low: 2 };
  return [...tickets].sort((a, b) => rank[a.priority] - rank[b.priority]);
}

function badge(t: Ticket): string {
  return t.priority === "urgent" ? "P1" : t.priority === "normal" ? "P2" : "P3";
}

new Ticket("Login broken", "urgnet"); // compiles
```

## After

```ts
class Priority {
  static readonly Urgent = new Priority("urgent", 0, "P1");
  static readonly Normal = new Priority("normal", 1, "P2");
  static readonly Low = new Priority("low", 2, "P3");
  private static readonly all = [Priority.Urgent, Priority.Normal, Priority.Low];

  private constructor(readonly code: string, readonly rank: number, readonly badge: string) {}

  static fromCode(code: string): Priority {
    const p = Priority.all.find((x) => x.code === code);
    if (!p) throw new Error(`unknown priority ${code}`);
    return p;
  }
}

class Ticket {
  constructor(public title: string, public priority: Priority) {}
}

const sortQueue = (tickets: Ticket[]) =>
  [...tickets].sort((a, b) => a.priority.rank - b.priority.rank);
const badge = (t: Ticket) => t.priority.badge;
```

## Why

- Invalid codes are rejected at the boundary (`fromCode`) and impossible in code.
- Rank and badge live with the code. Adding a priority means one new line.
- Signatures say `Priority`, not `string`.

## Steps

1. Create the class with a private constructor and one static instance per code. Keep the raw `code` as a field for persistence.
2. Add a `fromCode` factory that validates.
3. Change the owner's field type. Convert at the boundaries: `fromCode` when loading, `.code` when saving. Run the tests.
4. Move per-code data (labels, ranks, colors) from lookup tables into the class. Run the tests after each move.
5. Replace remaining string comparisons with instance comparisons (`=== Priority.Urgent`).

## Watch out

- In TypeScript, a string literal union (`type Priority = "urgent" | "normal" | "low"`) already gives compile-time safety with no runtime cost. Prefer it when there is no attached data. Other languages have `enum` with fields (Java, Kotlin, Rust, Swift); use those.
- If code branches on the type to *behave* differently, this is only a first step. Continue with [Replace Type Code with Subclasses](https://aipatterns.dev/refactoring/techniques/replace-type-code-with-subclasses.md) or [Replace Type Code with State/Strategy](https://aipatterns.dev/refactoring/techniques/replace-type-code-with-state-strategy.md).
- Persisted values must stay the same strings or numbers. Serialize `code`, never the object.
- Inverse: go back to a union type or plain constants with [Replace Magic Number with Symbolic Constant](https://aipatterns.dev/refactoring/techniques/replace-magic-number-with-symbolic-constant.md) when the class carries no data.

## Related

- Fixes [Primitive Obsession](https://aipatterns.dev/refactoring/smells/primitive-obsession.md) and the [Shotgun Surgery](https://aipatterns.dev/refactoring/smells/shotgun-surgery.md) of scattered lookup tables.
- Siblings: [Replace Type Code with Subclasses](https://aipatterns.dev/refactoring/techniques/replace-type-code-with-subclasses.md) and [Replace Type Code with State/Strategy](https://aipatterns.dev/refactoring/techniques/replace-type-code-with-state-strategy.md).

## Agent checklist

- [ ] The codes do not drive behavior through conditionals; otherwise a sibling technique fits.
- [ ] A language-native enum or union type was considered first.
- [ ] Only valid instances can be constructed; parsing validates.
- [ ] Persistence still stores the original code values.
- [ ] Tests pass.
