APIs rarely return data in the shape you need. You get a response with deep nesting, awkward field names, and fields you do not care about. Then you write boilerplate to reshape it. Over and over.

I got tired of writing the same mapping code in every project. The pattern was always the same: pick fields from the source, rename them, flatten nested structures. So I built a small library to handle it declaratively.

Skeleton Mapper lets you define the shape you want and get it. No loops. No repetitive assignments.

What It Does

The library takes a source object and a template. The template describes which fields to extract and where to put them. The output matches the template structure with values from the source.

Simple mappings are straightforward:

const source = {
  name: "Team 1",
  shield: "shield.jpg",
  university: 5
};

const template = {
  teamName: "name",
  image: "shield"
};

const result = skeletonMap(source, template);
// { teamName: "Team 1", image: "shield.jpg" }

Nested fields use dot notation. This flattens deep structures:

const source = {
  user: {
    profile: {
      email: "user@example.com"
    }
  }
};

const template = {
  userEmail: "user.profile.email"
};

const result = skeletonMap(source, template);
// { userEmail: "user@example.com" }

The template can also create new nesting. You control the output structure completely:

const source = {
  name: "Angel",
  team: {
    university: { name: "University 1" }
  }
};

const template = {
  athlete: { name: "name" },
  school: { name: "team.university.name" }
};

const result = skeletonMap(source, template);
// { athlete: { name: "Angel" }, school: { name: "University 1" } }

Arrays work too. Pass an array of objects and get an array of mapped objects:

const users = [
  { name: "Alice", role: { title: "Admin" } },
  { name: "Bob", role: { title: "User" } }
];

const template = {
  username: "name",
  permission: "role.title"
};

const result = skeletonMap(users, template);
// [{ username: "Alice", permission: "Admin" }, { username: "Bob", permission: "User" }]

How It Works

The library uses two functions internally. One finds values in nested objects. The other applies the template recursively.

The value finder splits dot-notation paths and walks down the object tree. Given "user.profile.email" and a source object, it steps through user, then profile, then returns email. If any step hits undefined, it stops and returns undefined.

The template processor walks through each key in the template. If the value is a string, it uses that as a path to find the source value. If the value is an object, it recurses with that object as a new template.

This recursive design handles arbitrary nesting on both sides. The source can be deeply nested. The output can be deeply nested. The mapping stays readable either way.

Handling Missing Fields

By default, missing fields are omitted from the output. If a path points to something that does not exist, that key simply does not appear in the result.

You can change this behavior with a flag. When addUndefinedFields is true, missing values become explicit undefined entries:

const source = { name: "Test" };
const template = { name: "name", age: "age" };

skeletonMap(source, template, false);
// { name: "Test" }

skeletonMap(source, template, true);
// { name: "Test", age: undefined }

This matters when you need to distinguish between “field not mapped” and “field mapped but missing.”

Design Decisions

I kept the API minimal on purpose. One function does everything. No configuration objects. No method chaining. No special syntax to learn.

The template is plain JavaScript. You can build it dynamically. You can store it in a config file. You can compose templates by spreading objects together.

Type safety was a tradeoff. TypeScript cannot infer the output type from a runtime template. The function returns a generic object type. For stricter typing, you can cast the result or wrap the call.

No dependencies. The library is around 40 lines of code. It does one thing and stays small.

When To Use It

Skeleton Mapper fits well when you need to reshape API responses before using them. Or when you want to extract a subset of fields from complex objects. Or when you need consistent data shapes across different sources.

It does not replace full-featured DTO libraries. It does not do validation. It does not handle type coercion. It just maps fields.

For simple reshaping tasks, that is enough.

Installation

Install from npm:

npm install skeleton-mapper

Import and use:

import { skeletonMap } from 'skeleton-mapper';

const result = skeletonMap(sourceData, yourTemplate);

What I Learned

Small, focused libraries age better than big ones. This code has needed almost no maintenance since 2020. The narrow scope means there is little to break.

Writing generic utilities taught me to think about edge cases upfront. What happens with undefined? What about arrays? What about empty objects? Handling these cleanly from the start saves debugging later.

Back to Projects