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
instructionsobject contains localized strings foren,es,it,tr,ru, andzh - Optional fields:
secondary_musclesappears as an optional string array, whileinstruction_stepsprovides partial language coverage - Legacy fields:
body_partduplicatescategoryfor backward compatibility - Media references:
media_idexists 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
InstructionsandExerciseinterfaces insrc/types/exercise.d.tsto match thedata/exercises.jsonstructure fromhasaneyldrm/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
resolveJsonModuleandesModuleInteropintsconfig.jsonto import the dataset as a typed module - Maintain type safety: Import the JSON into a typed constant using
ExerciseDatasetto 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:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →