{"record":{"id":"c35d50f874afda2a","repo":"moeru-ai/airi","slug":"the-file-does-not-contain-valid-json-c35d50","errorCode":null,"errorMessage":"The file does not contain valid JSON.","messagePattern":"The file does not contain valid JSON\\.","errorType":"validation","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"packages/stage-ui/src/features/devtools/motion/live2d/composables/recording.ts","lineNumber":87,"sourceCode":"  stopPlayback: () => void\n  loadRecording: (nextRecording: Live2DMotionRecording) => void\n  dispose: () => void\n}\n\n/**\n * Parses and validates a Live2D joystick recording at the file boundary.\n *\n * @example\n * parseLive2DMotionRecording('{\"format\":\"airi-live2d-motion/v6\", ...}')\n * // => a validated recording\n */\nexport function parseLive2DMotionRecording(raw: string): Live2DMotionRecording {\n  let input: unknown\n  try {\n    input = JSON.parse(raw)\n  }\n  catch {\n    throw new Error('The file does not contain valid JSON.')\n  }\n\n  const result = safeParse(live2dMotionRecordingSchema, input)\n  if (!result.success)\n    throw new Error('The file is not an AIRI Live2D motion recording.')\n\n  const { durationMs, samples } = result.output\n  if (samples[0].atMs !== 0)\n    throw new Error('The first motion sample must start at 0 ms.')\n\n  for (let index = 1; index < samples.length; index++) {\n    if (samples[index].atMs < samples[index - 1].atMs)\n      throw new Error('The motion samples must be in time order.')\n  }\n\n  if (samples.at(-1)!.atMs > durationMs)\n    throw new Error('A motion sample occurs after the recording duration.')\n","sourceCodeStart":69,"sourceCodeEnd":105,"githubUrl":"https://github.com/moeru-ai/airi/blob/9c213115f8bd0fff9e6eabab02b077ac32da21be/packages/stage-ui/src/features/devtools/motion/live2d/composables/recording.ts#L69-L105","documentation":"parseLive2DMotionRecording loads an AIRI Live2D motion recording from a JSON string. The very first step is JSON.parse; if it throws, the raw text is not JSON at all, so the parser stops with this dedicated message instead of leaking the raw SyntaxError. It surfaces when a user picks a wrong or corrupted file in the motion import flow.","triggerScenarios":"Calling parseLive2DMotionRecording(raw) where raw is not valid JSON: empty file, HTML error page saved as .json, truncated download, trailing commas/comments (JSONC), or a binary file selected by mistake.","commonSituations":"Users importing an exported motion file that was edited and broke; picking a different project's config file; BOM or encoding corruption; saving with a text editor that added non-JSON content; importing a YAML/JSON5 file.","solutions":["Verify the file is complete, valid JSON (e.g. JSON.parse in a linter or jq) before importing","Re-export the motion recording from the AIRI devtools recorder","Strip BOM and any trailing commas/comments, or convert from JSONC/YAML to strict JSON","Check the file was not truncated by an interrupted download/transfer"],"exampleFix":null,"handlingStrategy":"try-catch","validationCode":"function isValidJson(raw) {\n  try {\n    JSON.parse(raw)\n    return true\n  }\n  catch {\n    return false\n  }\n}","typeGuard":null,"tryCatchPattern":"try {\n  const recording = parseLive2DMotionRecording(raw)\n}\ncatch (error) {\n  if (error.message === 'The file does not contain valid JSON.')\n    showImportError('Selected file is not valid JSON. Re-export the motion recording.')\n  else throw error\n}","preventionTips":["Check file extension and preview the first bytes before import","Strip BOM characters before parsing","Warn users when a file was edited outside the app","Use jq or a JSON linter to verify exported files"],"tags":["json","parse","validation","file-import"],"backgroundTag":"invalid-json","analyzedSha":"9c213115f8bd0fff9e6eabab02b077ac32da21be","analyzedAt":"2026-09-02T04:27:24.639Z","contentChangedAt":"2026-09-02T04:27:24.639Z","schemaVersion":2},"datasetVersion":"2026-09-14T00:17:10.932Z"}