My startup — cybersecurity intelligence & big data with the world's largest fraud database
← Back to Blog
5 min read
April 10, 2026

Stop Building Mappings, Start Writing Rules

How I stopped maintaining hundreds of lines of mapping objects and started writing functions that derive the output instead.

Stop Building Mappings, Start Writing Rules
Engineering

Stop Building Mappings, Start Writing Rules

You've probably seen code like this everywhere:

TypeScript
export const SECTION_MAPPING: Record<Section, string> = {
    community: "Community",
    engineering: "Engineering",
    customers: "Customers",
    projects: "Projects",
    "open-source": "Open Source"
};

At first glance, nothing wrong with it.

But in real codebases this ends up turning into hundreds of lines of hand-written mappings.

And the weird part is: most of them follow the same pattern.


The pattern hiding in plain sight

Look at the transformation:

  • projects -> Projects
  • open-source -> Open Source
  • user_profile -> User Profile

It's always the same idea:

  1. split words (- or _)
  2. capitalize each word

So why are we writing the output instead of the rule?


Step 1: stop storing results, compute them

Instead of maintaining a mapping:

TypeScript
SECTION_MAPPING["open-source"];

Just derive it:

TypeScript
export function formatLabel(input: string): string {
    return input
        .replace(/[-_]/g, " ").split(" ")
        .map(word => word.charAt(0).toUpperCase() + word.slice(1))
        .join(" ");
}

Now the mapping disappears completely:

TypeScript
formatLabel("open-source"); // Open Source

No data file. No duplication. Just logic.


The thing that always breaks this simplicity

Real systems always introduce "special cases":

  • ai -> AI
  • sdk -> SDK
  • mcp -> MCP
  • waf -> WAF

And this is where people usually go back to object mappings:

TypeScript
const EXCEPTIONS: Record<string, string> = {
    ai: "AI",
    sdk: "SDK",
    mcp: "MCP",
    waf: "WAF"
};

But that's still just encoding a simple rule in a verbose way.


Step 2: reduce exceptions to a rule

These aren't really exceptions. They're just "always uppercase words".

So instead of mapping key -> value, just store intent:

TypeScript
const UPPERCASE_WORDS = ["ai", "sdk", "mcp", "waf"];

Now the function becomes:

TypeScript
export function formatLabel(input: string): string {
    return input.replace(/[-_]/g, " ").split(" ")
        .map(word => {
            const lower = word.toLowerCase();
            if (UPPERCASE_WORDS.includes(lower)) return lower.toUpperCase();
            return word.charAt(0).toUpperCase() + word.slice(1);
        }).join(" ");
};

What actually changed?

Not much in code size.

A lot in design.

Before:

  • you stored every output manually
  • mappings grew linearly with the product
  • every new label meant a new entry

After:

  • you encode one rule
  • plus a tiny set of overrides
  • everything else is derived

The real mental shift

Here's the thing I keep coming back to:

Most mapping objects aren't data. They're failed functions.

I mean that in the most practical sense possible. You already know how to transform open-source into Open Source. You've done it a thousand times. The only reason you're storing it in an object is because at some point someone decided "this is data, not logic."

But it doesn't have to be either/or.

Once this clicked for me, I started seeing it everywhere. The sidebar labels that don't match their routes. The permission names that get transformed into readable text. The feature flags that are just camelCase in, Title Case out.

The best part? You don't even need to refactor everything. Just next time you reach for a mapping object, ask yourself: what's the rule here?

Chances are, you already know it.

Author
Javier Romero González's avatar

Check my portfolio

Learn more about my work and projects!

Posts in:
Engineering
Version 2.4.7 — All rights reserved © Creative Commons Attribution - All Rights Reserves: CC BY-NC-ND 4.0 | TPEOficial LLC.