How to Define TypeScript Interfaces for Exercise Data: Complete Guide for the Exercises Dataset

Define an Exercise interface that models the JSON schema from hasaneyldrm/exercises-dataset, including multilingual instructions and optional secondary_muscles, then import the dataset with resolveJsonModule enabled for full type safety.

The hasaneyldrm/exercises-dataset repository provides a comprehensive collection of fitness exercises stored in data/exercises.json. To consume this data in TypeScript applications without losing compile-time safety, you need interfaces that reflect the exact schema documented in the README's Data Schema section. This guide demonstrates how to create TypeScript interfaces for exercise data that match the repository's structure and enable IDE autocomplete across your application.

Understanding the Exercise Data Schema

According to the hasaneyldrm/exercises-dataset source code, each exercise record in data/exercises.json follows a stable schema containing 1,324 entries with multilingual support. The data structure includes scalar fields like id and name, nested objects for instructions in six languages, and optional arrays for secondary muscle groups.

Key fields to model include:

  • Multilingual content: The instructions object contains localized strings for en, es, it, tr, ru, and zh
  • Optional fields: secondary_muscles appears as an optional string array, while instruction_steps provides partial language coverage
  • Legacy fields: body_part duplicates category for backward compatibility
  • Media references: media_id exists but actual media files are stored separately

Creating the TypeScript Interface Definitions

Create a dedicated declaration file at src/types/exercise.d.ts to house your type definitions. This approach keeps domain models centralized and allows the TypeScript compiler to resolve types across your entire project.

The Instructions Interface

First, model the multilingual instructions object that appears in every exercise record:

// src/types/exercise.d.ts
export interface Instructions {
  /** English instructions */
  en: string;
  /** Spanish instructions */
  es: string;
  /** Italian instructions */
  it: string;
  /** Turkish instructions */
  tr: string;
  /** Russian instructions */
  ru: string;
  /** Chinese instructions */
  zh: string;
}

The Exercise Interface

Next, define the main Exercise interface that composes these types and handles optional fields:

// src/types/exercise.d.ts
/** Represents a single exercise entry */
export interface Exercise {
  /** Unique numeric identifier, e.g. "0001" */
  id: string;
  /** Human-readable name of the exercise */
  name: string;
  /** Primary category / body part */
  category: string;
  /** Duplicate of `category`; kept for backward compatibility */
  body_part: string;
  /** Equipment required (or "body weight") */
  equipment: string;
  /** Full instructions per language */
  instructions: Instructions;
  /** Separate step-by-step list per language (optional) */
  instruction_steps?: Partial<Record<keyof Instructions, string[]>>;
  /** Primary muscle group */
  muscle_group: string;
  /** Additional muscles that are involved */
  secondary_muscles?: string[];
  /** Specific target muscle */
  target: string;
  /** Media reference ID (the media itself is not bundled) */
  media_id: string;
  /** Reserved fields – always `null` in this repo */
  image: null;
  gif_url: null;
  /** Timestamp of record creation */
  created_at: string;
}

/** An array of all exercises */
export type ExerciseDataset = Exercise[];

The instruction_steps field uses Partial<Record<keyof Instructions, string[]>> because future records might omit specific languages, requiring a type-safe optional map rather than a complete record.

Configuring TypeScript for JSON Imports

Before importing the dataset, enable JSON module resolution in your tsconfig.json. Add both resolveJsonModule and esModuleInterop to the compiler options:

{
  "compilerOptions": {
    "moduleResolution": "node",
    "esModuleInterop": true,
    "resolveJsonModule": true,
    "strict": true
  }
}

These settings allow TypeScript to import data/exercises.json as a typed module rather than treating it as an untyped resource.

Importing and Using the Typed Data

Once configured, import the JSON file and cast it to your defined interface for complete type safety throughout your application.

Loading the Dataset

Create a data loader module that imports and exports the typed dataset:

// src/data/loadExercises.ts
import exercises from '../../data/exercises.json';
import type { ExerciseDataset } from '../types/exercise';

const dataset: ExerciseDataset = exercises;

export default dataset;

Filtering by Equipment

Use standard array methods with full autocomplete support for exercise properties:

import dataset from './data/loadExercises';

const bodyWeightExercises = dataset.filter(e => e.equipment === 'body weight');
console.log(`Found ${bodyWeightExercises.length} body-weight exercises`);

Accessing Multilingual Instructions

Retrieve localized content safely using the typed language keys:

function getEnglishInstructions(id: string): string {
  const ex = dataset.find(e => e.id === id);
  if (!ex) throw new Error('Exercise not found');
  return ex.instructions.en;
}

Handling Optional Step-by-Step Instructions

When consuming the partial instruction_steps map, verify existence before accessing array indices:

function listStepByStep(id: string, lang: keyof Instructions): string[] {
  const ex = dataset.find(e => e.id === id);
  if (!ex?.instruction_steps?.[lang]) return [];
  return ex.instruction_steps[lang]!;
}

Building a Type-Safe API Endpoint

Use the interfaces to enforce response types in Express applications:

import express from 'express';
import dataset from './data/loadExercises';
import type { Exercise } from './types/exercise';

const app = express();

app.get('/api/exercises/:id', (req, res) => {
  const ex = dataset.find((e) => e.id === req.params.id) as Exercise | undefined;
  if (!ex) return res.status(404).json({ error: 'Not found' });
  res.json(ex);
});

app.listen(3000, () => console.log('API listening on :3000'));

Summary

  • Model the complete schema: Define Instructions and Exercise interfaces in src/types/exercise.d.ts to match the data/exercises.json structure from hasaneyldrm/exercises-dataset
  • Handle multilingual data: Use Partial<Record<keyof Instructions, string[]>> for optional step-by-step instructions that may not cover all six languages
  • Enable JSON imports: Set resolveJsonModule and esModuleInterop in tsconfig.json to import the dataset as a typed module
  • Maintain type safety: Import the JSON into a typed constant using ExerciseDataset to catch schema mismatches at compile time

Frequently Asked Questions

What fields are required in the Exercise interface?

The required fields include id, name, category, body_part, equipment, instructions, muscle_group, target, media_id, image, gif_url, and created_at. According to the source code in data/exercises.json, secondary_muscles and instruction_steps are optional and use TypeScript optional chaining (?) in the interface definition.

How do I handle the six-language support in TypeScript?

The Instructions interface defines six required string properties: en, es, it, tr, ru, and zh. When accessing these in your code, use exercise.instructions[languageCode] where languageCode is constrained to keyof Instructions for type safety.

Why does the interface use Partial<Record> for instruction_steps?

The instruction_steps field maps language codes to string arrays, but not every exercise includes steps for all six languages. Using Partial<Record<keyof Instructions, string[]>> creates a type-safe structure where any language key might be undefined, preventing runtime errors when accessing exercise.instruction_steps?.en.

Can I use these interfaces with validation libraries like Zod?

Yes. You can transform these TypeScript interfaces into Zod schemas by calling z.object() with the same field structure. This provides runtime validation while maintaining the static type definitions, ensuring that data loaded from data/exercises.json conforms to the expected schema before processing.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →