Skip to content

LC-JSON 1.0-rc.2 — first announced release candidate

Pre-release
Pre-release

Choose a tag to compare

@bantonym bantonym released this 30 May 16:17
· 2 commits to main since this release
Immutable release. Only release title and notes can be modified.
v1.0-rc.2
6d92758

LC-JSON v1.0-rc.2

Prepared: 2026-05-30 · Status: Public release candidate (pre-release), the first formally announced prerelease
Specification: https://lc-json.org · Repository: https://github.com/lc-json/specification
Schemas: https://lc-json.org/1.0-rc.2/ · License: Apache 2.0

This is the first publicly announced release candidate of LC-JSON (Learning Content JSON), an open specification for portable learning content, built around a simple premise:

Educational content should remain readable, structured, reusable, and under the control of the people who create it.

LC-JSON defines a JSON interchange format for courses, units, lessons, instructional content, exercises, quizzes, questions, learning objectives, and accessibility metadata — designed to move cleanly between authoring tools, learning-management systems, and delivery platforms rather than being locked inside any one of them.

LC-JSON is a content-layer format — complementary to, not competing with, the established LMS interop standards: LTI (tool launch and grade passback), OneRoster (rosters), xAPI (activity records), SCORM (runtime delivery). The RATIONALE.md document in the specification covers the full landscape and what LC-JSON is not.


What LC-JSON looks like

A minimal QuestionSet — one of the two artifact types (the other is a hierarchical Course):

{
  "$schema": "https://lc-json.org/1.0-rc.2/question-set.schema.json",
  "documentType": "questionSet",
  "specVersion": "1.0",
  "title": "Geography — Capitals",
  "language": "en",
  "questions": [
    {
      "type": "shortAnswer",
      "globalId": "550e8400-e29b-41d4-a716-446655440001",
      "prompt": "What is the capital of France?",
      "acceptedAnswers": ["Paris"],
      "points": 1.0,
      "hint": "The city sits on the River Seine in northern France and has been the nation's political and cultural center for over a thousand years.",
      "feedback": {
        "correct": "Correct — Paris. Founded as the Roman town of Lutetia on the Île de la Cité, it has been France's capital since the late 10th century and is today the country's largest city and its cultural and economic heart.",
        "incorrect": "Not quite — the answer is Paris, home to the Eiffel Tower, the Louvre, and Notre-Dame. As the line from the film Sabrina goes, 'Paris is always a good idea.'"
      }
    }
  ]
}

The hint and feedback blocks are optional — shown here to illustrate what a fully-authored question carries. Omitted optional fields fall back to sensible defaults: caseSensitivefalse (so Paris, paris, and PARIS all match), and difficulty5.0. Empty-optional fields like title and tags are simply left out rather than written as "" or [] — keeping documents legible and diffs clean.

shortAnswer is used here deliberately: it is a real-content type whose prompt legitimately is the question, so a single short headline reads cleanly. For fuller worked documents — a complete course, unit, lesson, and the exercise/quiz item shapes — see the spec's examples/ tree: course-minimal.json, unit-minimal.json, lesson-minimal.json, 11-exercise-item.json, 12a-graded-quiz-item.json, 12b-ungraded-quiz-item.json, and sample-course-with-questions.json at https://github.com/lc-json/specification/tree/main/examples.

A Course wraps these same question shapes inside exercise/quiz items, lessons, and units — identical document envelope, hierarchical body.


Changes from the internal rc.1 candidate

  • prompt: minLength 10; non-authoritative for the eight symbolic question types.
    • prompt stays required on every question. An empty string "" is valid.
    • For the four real-content types (true/false, multiple choice, short answer, essay) prompt is the question and remains authoritative; the reference validator flags an empty prompt on these as an ERROR.
    • For the eight symbolic types (gap-fill family, sentence transformation, matching, ordering, placement) the structured fields carry the question's meaning; prompt MAY be empty or MAY carry a brief producer-derived readable summary. Consumers MUST NOT rely on a symbolic prompt's content for scoring, rendering, equality, or deduplication.
  • Reserved/unknown-type preservation language. NORMATIVE.md §6.2 and §6.4 now require preservation of every member, value, and nested structure across read/write cycles, replacing the earlier "verbatim" / "byte-equivalent" wording. Key order within JSON objects is producer-discretion per RFC 8259 §4 (SHOULD for authoring ergonomics, not MUST for consumers).
  • TrueFalseQuestion correctAnswer import normalization. Explicitly labelled in question-types-reference.md as a pre-1.0 lenient migration affordance, not conforming behavior. The schema requires a JSON boolean; conforming producers MUST emit a JSON boolean; conforming consumers in strict mode MUST reject non-boolean values per §5.1.

Backward compatibility: every document valid under the rc.1 schemas remains valid under rc.2. The prompt change is a non-breaking widening (minLength: 1 → 0 only relaxes the constraint; object shape unchanged). The preservation-language and TF-normalization changes tighten normative-prose precision; no schema changes, no wire-format changes.


What's in this release candidate

  • Two artifact types: Course (course → units → lessons → items) and QuestionSet (flat list of questions)
  • Five item types: content, exercise, quiz, contentsequence, signpost
  • 12 question types with full per-type schemas — gap-fill, multiple choice, true/false, the cloze family, short answer, essay, matching, ordering, placement, sentence transformation; 7 further types reserved for a future minor version
  • Learning objectives, metadata, and portable identifiers throughout the hierarchy
  • Accessibility metadata preservation built into base conformance
  • 23 JSON Schemas (Draft 7), a conformance test corpus, a reference validator, and worked examples

Try it

Every document declares its schema by URL, so any JSON Schema (Draft 7) validator works:

pip install jsonschema
python tools/validate_course.py --course-path your-document.json

A document is conforming LC-JSON if it validates against the published schemas at its declared $schema URL and satisfies the producer/consumer requirements in NORMATIVE.md. A 38-fixture conformance corpus (13 valid, 25 invalid) plus the reference validator define the bright line.


What's stable, what may still change

Stable — safe to build against:

  • The document envelope ($schema, documentType, specVersion) and flat-root shape
  • The two artifact types and five item types
  • The 12 question-type shapes and their canonical camelCase discriminators
  • The /1.0-rc.2/ schema URLs — immutable for the life of the specification

May still be refined before v1.0 final (target: 2026-06-30):

  • Additional conformance fixtures and validation-tooling coverage
  • Accessibility-profile deepening
  • Minor cleanup of optional fields
  • Documentation clarifications

No breaking changes to the wire format are expected. The /1.0/ URL space is reserved for the final release and will be a deliberate re-publication — a document pinned to /1.0-rc.2/ continues to validate against rc.2 indefinitely. The earlier internal /1.0-rc.1/ schema set remains served and frozen but was never publicly announced.


Areas still under discussion

Deferred to keep the foundational interchange model stable first: accessibility conformance profiles, packaging and distribution conventions, interactive-activity extensions, analytics and feedback metadata, and rich-media embedding conventions.

Two prompt-specific items are explicitly deferred, with no change to the rc.2 contract beyond minLength: 0:

  • Future treatment of prompt for symbolic types — whether a later version makes it optional/omittable rather than required-with-empty — left for post-1.0 review once there is real-world implementation experience.
  • prompt rules for the 7 reserved question types — deferred to v1.1, when those types are implemented and gain per-type schemas. Under rc.2 an empty prompt on a reserved type is permitted (no domain rule fires).

A separate design question — surfaced during the rc.2 cycle but not changed in this release — concerns grading semantics for unsupported question types:

  • Grading semantics for unsupported question types. §6.2 currently keeps an unsupported question's possible points in the item's total while setting earned points to 0 — the item maximum is not silently reduced. This preserves grade comparability across consumers but means a learner whose consumer cannot render a question has no path to those points (and the item percentage absorbs the gap). Open question for future versions: should consumers continue to leave unsupported items quietly non-performing — possibly under-counting against the author/instructor's grading intent — or should they apply maximum fairness to learners by preemptively setting the unsupported question's possible points to 0? Feedback from implementers and instructional designers welcome via issues on the spec repository.

Two further questions surfaced during the rc.2 review and are explicitly deferred:

  • Strict RFC 4122 UUID validation. The schema regex currently checks UUID shape only (8-4-4-4-12 hex), not RFC 4122 version or variant bits. The prose was aligned in rc.2 to reflect shape-only enforcement ("RFC 4122 UUID, any version; shape-only validation"). Open question for future versions: tighten the schema to enforce specific UUID versions (e.g. v4) and the RFC 4122 variant bits, or formally adopt shape-only as the contract. Feedback from implementers welcome via issues on the spec repository.
  • Deterministic semantics for points: null. question-base.schema.json permits points: null, but for reserved and unknown question types §6.2 requires possible points to count toward the item's total — null has no deterministic value there. Open question for future versions: forbid null for scored questions, or define a deterministic default (e.g. null1.0) that consumers MUST apply. Feedback from implementers and instructional designers welcome via issues on the spec repository.

Three further standards-surface questions surfaced during the rc.2 review cycle. None block rc.2, all are flagged here for resolution before or during 1.0 final:

  • Normative authority of the reference validator. tools/validate_course.py enforces several domain rules (e.g. MCQ MUST have at least one option with points > 0; optionsAndPoints MUST cover every entry in options; cloze marker-set equality with answer-key set) as ERROR-tier. These rules are documented in VALIDATION.md (informative catalog) and question-types-reference.md (per-type guidance), but the binding RFC 2119 prose for them does not currently live in NORMATIVE.md. Open question for 1.0 final: promote every ERROR-tier domain-validator rule that conforming consumers MUST replicate into explicit normative prose in NORMATIVE.md, or formally state that the reference validator's domain pass is a reference-tool check (recommended but not normatively binding on third-party consumers). The corpus (run_corpus.py) currently encodes the former interpretation by pinning every ERROR-tier rule with an invalid fixture under --strict.
  • HTML processing model: strict-reject vs. recovery-sanitize. HTML_SAFETY.md says forbidden elements (<script>, <iframe>, event handlers, javascript:/vbscript: URLs) MUST be stripped by the sanitizer; NORMATIVE.md §11 and the same HTML_SAFETY.md validator-severity table say consumers MUST reject documents containing those constructs as ERROR-tier violations. Both statements are true in different processing stages — validation/import rejects, rendering fallback (if a non-conforming document is rendered at all) strips — but the current prose does not separate the stages cleanly enough for a first-time reader. Open question for 1.0 final: explicitly split the processing model into "validation stage: reject" and "recovery rendering stage (out of strict conformance): sanitize," so implementers do not have to infer which MUST wins.
  • Language-tag model: ISO 639-1 root vs. BCP 47 inline. Root language and supportLanguage are ISO 639-1 (en, es, pt, …); inline HTML lang attributes inside ContentItem.html follow BCP 47 (en-US, pt-BR, zh-Hant, es-419, …). Courses targeting regional varieties (Brazilian vs. European Portuguese, Simplified vs. Traditional Chinese, US vs. UK English) cannot express that distinction at the document root in rc.2 — only inline. Open question for 1.0 final: keep root language deliberately coarse (and document the limitation), widen it to BCP 47 (single model end-to-end), or add a sibling root field for regional variant. The accessibility profile's WCAG 3.1.1 obligation is satisfied either way; this is an authoring expressivity question.

Extensions & compatibility

LC-JSON is designed to grow without invalidating existing documents. Tools may add namespaced extensions (x-<namespace> members) for local metadata, provided base document validity is preserved and core structural semantics stay intact. Consumers SHOULD ignore unrecognized extension members while preserving them across read/write cycles. The specification distinguishes normative requirements (MUST/SHOULD/MAY) from implementation recommendations and ecosystem conventions.

Implementers are encouraged to preserve unknown fields, prefer graceful degradation over hard failure, avoid assumptions about property ordering, and keep content structure separate from presentation.


Feedback

This release opens the public review phase ahead of stable v1.0. Implementation reports, edge cases, and interoperability findings are especially welcome from LMS and EdTech developers, instructional designers, accessibility specialists, educational publishers, and open-educational-resource communities.

Open an issue or discussion at https://github.com/lc-json/specification.