pcottle/learnGitBranching · error · Error
level progress must be a JSON object
Error message
level progress must be a JSON object
What it means
LevelStore._normalizeImportedSolvedMap() validates the top-level shape of level-progress JSON passed to importLevelProgress(). It rejects null, non-objects, and arrays, throwing a plain Error('level progress must be a JSON object'). The expected shape is a map of levelID -> true or {solved, best}.
Source
Thrown at src/js/stores/LevelStore.js:65
} catch (e) {
console.warn('local storage failed', e);
}
function _syncToStorage() {
try {
localStorage.setItem(SOLVED_MAP_STORAGE_KEY, JSON.stringify(_solvedMap));
} catch (e) {
console.warn('local storage failed on set', e);
}
}
function _cloneSolvedMap() {
return JSON.parse(JSON.stringify(_solvedMap));
}
function _normalizeImportedSolvedMap(progress) {
if (!progress || typeof progress !== 'object' || Array.isArray(progress)) {
throw new Error('level progress must be a JSON object');
}
var normalized = {};
Object.keys(progress).forEach(function(levelID) {
var levelData = progress[levelID];
// Backwards compatibility with the old storage format.
if (levelData === true) {
normalized[levelID] = true;
return;
}
if (!levelData || typeof levelData !== 'object' || Array.isArray(levelData)) {
throw new Error('invalid progress data for level: ' + levelID);
}
normalized[levelID] = {
solved: levelData.solved === true,View on GitHub (pinned to 5b09d0ff96)
Solutions
- Re-shape the imported JSON so the root is an object keyed by level ID, e.g. {"intro-1": true}
- If exporting progress yourself, dump the same structure _cloneSolvedMap() produces
- Wrap importLevelProgress in try/catch and surface a friendly message for malformed files
Example fix
// before
importLevelProgress(["intro-1"]);
// after
importLevelProgress({"intro-1": true}); Defensive patterns
Strategy: type-guard
Validate before calling
function isProgressMap(v) {
return Boolean(v) && typeof v === 'object' && !Array.isArray(v);
}
if (!isProgressMap(data)) { reject('expected a JSON object keyed by level ID'); } Type guard
function isProgressMap(v) {
return v !== null && typeof v === 'object' && !Array.isArray(v);
} Try / catch
try { importLevelProgress(data); } catch (e) { if (/must be a JSON object/.test(e.message)) { showError('invalid save file'); } else throw e; } Prevention
- Export and import using the same map structure (_cloneSolvedMap output)
- Validate parsed JSON shape before importing user-supplied files
When it happens
Trigger: Calling importLevelProgress() with a JSON array of level IDs, a string/number, or null/undefined — e.g. importing a save file whose root is [{...}] instead of {id: ...}.
Common situations: Hand-edited or machine-exported save files with the wrong root structure; mixing up the export format (array of records vs keyed map); passing JSON.parse output of an empty file (null).
Understand the failure class
Background: Schema validation failed / invalid input schema: payload rejected because its shape doesn't match the expected schema — this error's family across 28 libraries.
Related errors
AI-assisted analysis of pcottle/learnGitBranching@5b09d0ff96 (2026-08-27).
Data as JSON: /api/errors/a4d70311ae1c2f8d.
Report an issue: GitHub.