{"record":{"id":"a4d70311ae1c2f8d","repo":"pcottle/learnGitBranching","slug":"level-progress-must-be-a-json-object","errorCode":null,"errorMessage":"level progress must be a JSON object","messagePattern":"level progress must be a JSON object","errorType":"validation","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"src/js/stores/LevelStore.js","lineNumber":65,"sourceCode":"} catch (e) {\n  console.warn('local storage failed', e);\n}\n\nfunction _syncToStorage() {\n  try {\n    localStorage.setItem(SOLVED_MAP_STORAGE_KEY, JSON.stringify(_solvedMap));\n  } catch (e) {\n    console.warn('local storage failed on set', e);\n  }\n}\n\nfunction _cloneSolvedMap() {\n  return JSON.parse(JSON.stringify(_solvedMap));\n}\n\nfunction _normalizeImportedSolvedMap(progress) {\n  if (!progress || typeof progress !== 'object' || Array.isArray(progress)) {\n    throw new Error('level progress must be a JSON object');\n  }\n\n  var normalized = {};\n  Object.keys(progress).forEach(function(levelID) {\n    var levelData = progress[levelID];\n\n    // Backwards compatibility with the old storage format.\n    if (levelData === true) {\n      normalized[levelID] = true;\n      return;\n    }\n\n    if (!levelData || typeof levelData !== 'object' || Array.isArray(levelData)) {\n      throw new Error('invalid progress data for level: ' + levelID);\n    }\n\n    normalized[levelID] = {\n      solved: levelData.solved === true,","sourceCodeStart":47,"sourceCodeEnd":83,"githubUrl":"https://github.com/pcottle/learnGitBranching/blob/5b09d0ff9678cad164f5a7488a279a44f90d7256/src/js/stores/LevelStore.js#L47-L83","documentation":"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}.","triggerScenarios":"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: ...}.","commonSituations":"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).","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"],"exampleFix":"// before\nimportLevelProgress([\"intro-1\"]);\n// after\nimportLevelProgress({\"intro-1\": true});","handlingStrategy":"type-guard","validationCode":"function isProgressMap(v) {\n  return Boolean(v) && typeof v === 'object' && !Array.isArray(v);\n}\nif (!isProgressMap(data)) { reject('expected a JSON object keyed by level ID'); }","typeGuard":"function isProgressMap(v) {\n  return v !== null && typeof v === 'object' && !Array.isArray(v);\n}","tryCatchPattern":"try { importLevelProgress(data); } catch (e) { if (/must be a JSON object/.test(e.message)) { showError('invalid save file'); } else throw e; }","preventionTips":["Export and import using the same map structure (_cloneSolvedMap output)","Validate parsed JSON shape before importing user-supplied files"],"tags":["level-store","import","json-validation"],"backgroundTag":"schema-validation-failed","analyzedSha":"5b09d0ff9678cad164f5a7488a279a44f90d7256","analyzedAt":"2026-08-27T13:53:49.057Z","schemaVersion":2},"datasetVersion":"2026-08-27T19:17:21.184Z"}