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

> Define TypeScript interfaces for exercise data using the exercises dataset. Learn to model JSON schemas with multilingual instructions and optional muscles for type-safe imports.

- Repository: [Hasan Emir Yıldırım/exercises-dataset](https://github.com/hasaneyldrm/exercises-dataset)
- Tags: how-to-guide
- Published: 2026-07-31

---

**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`](https://github.com/hasaneyldrm/exercises-dataset/blob/main/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`](https://github.com/hasaneyldrm/exercises-dataset/blob/main/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`](https://github.com/hasaneyldrm/exercises-dataset/blob/main/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:

```typescript
// 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:

```typescript
// 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`](https://github.com/hasaneyldrm/exercises-dataset/blob/main/tsconfig.json). Add both `resolveJsonModule` and `esModuleInterop` to the compiler options:

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

```

These settings allow TypeScript to import [`data/exercises.json`](https://github.com/hasaneyldrm/exercises-dataset/blob/main/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:

```typescript
// 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:

```typescript
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:

```typescript
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:

```typescript
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:

```typescript
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`](https://github.com/hasaneyldrm/exercises-dataset/blob/main/src/types/exercise.d.ts) to match the [`data/exercises.json`](https://github.com/hasaneyldrm/exercises-dataset/blob/main/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`](https://github.com/hasaneyldrm/exercises-dataset/blob/main/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`](https://github.com/hasaneyldrm/exercises-dataset/blob/main/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`](https://github.com/hasaneyldrm/exercises-dataset/blob/main/data/exercises.json) conforms to the expected schema before processing.