Open Tests ZIP Format Specification

Authoritative reference for export/import compatibility

Overview

This document specifies the on-disk format used by Open Tests for exporting and importing tests as a single ZIP archive. It is intended for developers who want to generate compatible ZIPs or process exported tests.

All JSON files MUST be UTF-8 encoded. Paths inside JSON MUST use forward slashes (/).

Archive Layout

A valid archive MUST contain:

  • test_settings.json – Top-level test metadata and behavior flags.
  • questions/ – Folder containing one JSON file per question.

Optional entries:

  • assets/ – Folder containing media referenced by questions and options.

Example structure:

my-test.zip
└── /
    ├── test_settings.json
    ├── questions/
    │   ├── 001.json
    │   ├── 002.json
    │   └── 003.json
    ├── assets/
    │   ├── q001/
    │   │   ├── question.png
    │   │   └── option_1.webp
    │   └── q002/
    │       └── left_1.jpg

Notes:
- File names inside questions/ are not strictly prescribed; any .json files in this folder are considered. Zero-padded numeric prefixes are RECOMMENDED to preserve order for humans.
- The assets/ folder and its inner layout are not strictly prescribed; media are referenced by relative paths under assets/.

test_settings.json

Top-level test configuration. Example:

{
  "title": "Podstawy JavaScriptu",
  "description": "Test wprowadzający do JS",
  "allowScrolling": false,
  "showAnswerAfterQuestion": false,
  "showAnswersAtEnd": true,
  "randomizeQuestions": false,
  "randomizeAnswers": false,
  "timeLimit": 30,
  "passThreshold": 60
}

Field reference:

  • title (string, required): Human-readable name. Non-empty on export.
  • description (string, optional): Free text.
  • allowScrolling (boolean, required): Allow vertical scrolling between questions in a single view.
  • showAnswerAfterQuestion (boolean, required): Reveal correct answers after each question.
  • showAnswersAtEnd (boolean, required): Reveal correct answers after finishing the test.
  • randomizeQuestions (boolean, required): Shuffle question order.
  • randomizeAnswers (boolean, required): Shuffle options within questions that have options.
  • timeLimit (integer|null, optional): Time limit in MINUTES for the entire test. null or omitted means no limit.
  • passThreshold (integer|null, optional): Minimum percentage (0–100). null or omitted means no threshold.

Questions (questions/*.json)

Each file in questions/ describes a single question. Example (common shape):

{
  "id": 1,
  "type": "single-choice",
  "content": "Który operator służy do konkatenacji stringów w JS?",
  "media": "assets/q001/question.png",
  "timeLimit": 45,
  "maxPoints": 1,
  "typeSpecificData": { /* varies by type */ }
}

Common fields:

  • id (integer, required): Question identifier. On import, IDs may be normalized to sequential values in the UI; keep values unique.
  • type (string, required): One of single-choice, multiple-choice, ordering, matching, short-answer, open-question.
  • content (string, required): The question text.
  • media (string|absent, optional): Relative path under assets/ to an image or video associated with the question.
  • timeLimit (integer|null, optional): Per-question time limit in SECONDS. null/omitted means no limit.
  • maxPoints (number, required): Positive value (e.g., 1, 0.5, 2.0).
  • typeSpecificData (object, required): Payload that depends on type (see below).

Media Representation

Wherever a media field appears (question-level or inside options/items/pairs), the exported JSON stores a STRING path relative to the archive root, starting with assets/. Example: "assets/q001/option_1.webp".

Supported formats (by MIME/extension):

  • Images: image/jpeg (.jpg, .jpeg), image/png (.png), image/gif (.gif), image/webp (.webp), image/svg+xml (.svg).
  • Video: video/mp4 (.mp4), video/webm (.webm).

Guidelines:

  • Individual media files SHOULD be ≤ 10 MB.
  • Paths MUST NOT be absolute; use forward slashes.

Type-Specific Payloads

Below are precise structures for typeSpecificData per question type. Unspecified additional fields SHOULD NOT be used.

1) single-choice

{
  "options": [
    { "id": 1, "text": "+", "media": "assets/q001/option_1.webp" },
    { "id": 2, "text": "&", "media": null },
    { "id": 3, "text": "||" }
  ],
  "correctOptionId": 1
}
  • options (array, required): List of selectable options. Each option: { id: integer, text: string, media?: string }.
  • correctOptionId (integer, required): Must equal one of the options[].id.

Constraints:

  • At least 2 options.
  • Exactly one correct option.

2) multiple-choice

{
  "options": [
    { "id": 1, "text": "Array", "media": null },
    { "id": 2, "text": "Object" },
    { "id": 3, "text": "Map" }
  ],
  "correctOptionIds": [2, 3],
  "partialCredit": true
}
  • options (array, required): Same shape as single-choice options.
  • correctOptionIds (array<int>, required): One or more option IDs from options[].id.
  • partialCredit (boolean, required): Award partial credit for partially correct selections.

Constraints:

  • At least 2 options.
  • correctOptionIds.length >= 1.

3) ordering

{
  "items": [
    { "id": 1, "text": "Pobierz dane", "media": null },
    { "id": 2, "text": "Przetwórz dane" },
    { "id": 3, "text": "Wyświetl wynik" }
  ]
}
  • items (array, required): Items listed in the CORRECT order. Each item: { id: integer, text: string, media?: string }.

Constraints:

  • At least 2 items.

4) matching

{
  "pairs": [
    {
      "id": 1,
      "left":  { "text": "HTML", "media": null },
      "right": { "text": "Struktura", "media": null }
    },
    {
      "id": 2,
      "left":  { "text": "CSS" },
      "right": { "text": "Prezentacja" }
    }
  ]
}
  • pairs (array, required): Correctly matched left/right items.
  • Each pair: { id: integer, left: { text: string, media?: string }, right: { text: string, media?: string } }.

Constraints:

  • At least 1 pair.

5) short-answer

{
  "correctAnswers": ["ECMAScript", "ES"],
  "exactMatch": false
}
  • correctAnswers (array<string>, required): Acceptable answers.
  • exactMatch (boolean, required): If true, user answer must match exactly (case sensitivity is implementation-defined; creators should normalize where relevant).

Constraints:

  • At least 1 answer.

6) open-question

{
  "gradingKey": "Oceń poprawność koncepcji, przykład i uzasadnienie."
}
  • gradingKey (string, required): Rubric/instructions for manual grading.

Validation Rules (Summary)

  • test_settings.json
    • title non-empty string.
    • passThreshold either null or integer in [0, 100].
    • timeLimit either null or positive integer (minutes).
  • Questions
    • id unique within the archive; positive integer recommended.
    • type is one of the supported values.
    • content non-empty string.
    • maxPoints positive number (> 0).
    • Per-type constraints as listed above.
    • Any media strings must point under assets/ if present.

Field Limits (Normative)

This section defines explicit min/max limits for numeric fields and string lengths. Unless specified otherwise, strings refer to character counts and integers are base-10 whole numbers.

  • test_settings.json
    • title (string): length 1–200.
    • description (string): length 0–2000.
    • allowScrolling, showAnswerAfterQuestion, showAnswersAtEnd, randomizeQuestions, randomizeAnswers (boolean): must be boolean.
    • timeLimit (integer|null, minutes): null or 1–1440.
    • passThreshold (integer|null, percent): null or 0–100.
  • Questions (common fields)
    • id (integer): 1–2,147,483,647; unique within archive.
    • type (enum string): one of the supported values.
    • content (string): length 1–2000.
    • media (string path): if present, length 1–300 and must start with assets/.
    • timeLimit (integer|null, seconds): null or 5–3600.
    • maxPoints (number): 0.01–100.00; up to 2 decimal places.
  • single-choice typeSpecificData
    • options (array): 2–20 items.
    • options[].id (integer): 1–2,147,483,647; unique within the question.
    • options[].text (string): length 1–200.
    • options[].media (string path): if present, length 1–300 and must start with assets/.
    • correctOptionId (integer): must equal one of options[].id.
  • multiple-choice typeSpecificData
    • options (array): 2–20 items (same per-option limits as single-choice).
    • correctOptionIds (array<int>): length 1–options.length; values unique; each must exist in options[].id.
    • partialCredit (boolean): must be boolean.
  • ordering typeSpecificData
    • items (array): 2–20 items.
    • items[].id (integer): 1–2,147,483,647; unique within the question.
    • items[].text (string): length 1–200.
    • items[].media (string path): if present, length 1–300 and must start with assets/.
  • matching typeSpecificData
    • pairs (array): 1–20 pairs.
    • pairs[].id (integer): 1–2,147,483,647; unique within the question.
    • left.text / right.text (string): length 1–200 each.
    • left.media / right.media (string path): if present, length 1–300 and must start with assets/.
  • short-answer typeSpecificData
    • correctAnswers (array<string>): 1–50 items.
    • correctAnswers[] (string): length 1–500.
    • exactMatch (boolean): must be boolean.
  • open-question typeSpecificData
    • gradingKey (string): length 1–2000.
  • Media paths and sizes
    • Path format: relative, forward slashes, start with assets/.
    • Path length: 1–300 characters.
    • File size guideline: ≤ 10 MB per file.

Ordering and IDs

  • On export, files in questions/ may be named with numeric prefixes for readability, but the importer does not require a specific naming convention.
  • On import, the application may normalize question IDs to a sequential order for display. Keep id values unique to avoid ambiguity.

Internationalization

All text fields (title, description, content, option texts, grading keys, etc.) support arbitrary UTF-8 content, including diacritics and RTL scripts.

Minimal Valid Example

minimal.zip
└── /
    ├── test_settings.json
    └── questions/
        └── 001.json

test_settings.json:

{
  "title": "Minimalny test",
  "description": "",
  "allowScrolling": false,
  "showAnswerAfterQuestion": false,
  "showAnswersAtEnd": true,
  "randomizeQuestions": false,
  "randomizeAnswers": false,
  "timeLimit": null,
  "passThreshold": null
}

questions/001.json:

{
  "id": 1,
  "type": "single-choice",
  "content": "1 + 1 = ?",
  "maxPoints": 1,
  "typeSpecificData": {
    "options": [
      { "id": 1, "text": "2" },
      { "id": 2, "text": "3" }
    ],
    "correctOptionId": 1
  }
}

Compatibility Notes

  • Time units differ by context: test_settings.timeLimit is in MINUTES, while question.timeLimit is in SECONDS.
  • assets/ is optional; absence of assets/ is valid if no media fields are present.
  • Additional unknown fields are ignored by the importer and SHOULD NOT be relied upon.