{"record":{"id":"528a4ecada68a3a2","repo":"heygen-com/hyperframes","slug":"chain-file-is-not-valid-json-err-as-error-mes","errorCode":null,"errorMessage":"Chain file is not valid JSON: ${(err as Error).message}","messagePattern":"Chain file is not valid JSON: (.+?)","errorType":"validation","errorClass":"AudioFxChainError","httpStatus":null,"severity":"error","filePath":"packages/core/src/audioFx.ts","lineNumber":765,"sourceCode":"\nexport class AudioFxChainError extends Error {\n  constructor(message: string) {\n    super(message);\n    this.name = \"AudioFxChainError\";\n  }\n}\n\n/**\n * Parse a chain file. Unknown effect ids are rejected rather than skipped: a\n * chain that silently loses a node would render differently from the project\n * the author saved, which is worse than refusing to render at all.\n */\nexport function parseAudioFxChain(json: string): HfAudioFxChain {\n  let raw: unknown;\n  try {\n    raw = JSON.parse(json);\n  } catch (err) {\n    throw new AudioFxChainError(`Chain file is not valid JSON: ${(err as Error).message}`);\n  }\n  if (typeof raw !== \"object\" || raw === null) {\n    throw new AudioFxChainError(\"Chain file must be a JSON object.\");\n  }\n  const obj = raw as { version?: unknown; nodes?: unknown };\n  if (obj.version !== HF_AUDIO_FX_CHAIN_VERSION) {\n    throw new AudioFxChainError(`Unsupported chain version: ${String(obj.version)}`);\n  }\n  if (!Array.isArray(obj.nodes)) {\n    throw new AudioFxChainError(\"Chain file is missing a `nodes` array.\");\n  }\n  const nodes: HfAudioFxNode[] = obj.nodes.map((n, i) => {\n    if (typeof n !== \"object\" || n === null) {\n      throw new AudioFxChainError(`Node ${i} is not an object.`);\n    }\n    const node = n as { type?: unknown; enabled?: unknown; params?: unknown };\n    if (typeof node.type !== \"string\" || !BY_ID.has(node.type)) {\n      throw new AudioFxChainError(`Node ${i} has unknown effect type: ${String(node.type)}`);","sourceCodeStart":747,"sourceCodeEnd":783,"githubUrl":"https://github.com/heygen-com/hyperframes/blob/c2996c8626135db5253519359d8a063d3bafad8d/packages/core/src/audioFx.ts#L747-L783","documentation":"parseAudioFxChain() wraps JSON.parse in a try/catch and re-throws as AudioFxChainError when the chain file string is not valid JSON. The original parse error message is interpolated. This is the first validation gate when loading a serialized chain (the data-fx-chain attribute or a chain file).","triggerScenarios":"Passing a malformed JSON string to parseAudioFxChain — unclosed brace, trailing comma, single quotes, unescaped characters, a truncated/cut-off string, or a chain attribute that was hand-edited and broke syntax.","commonSituations":"Manually editing the data-fx-chain attribute and introducing a syntax error; a build step mangling/escaping the attribute; reading a chain file that was partially written or corrupted on disk; copy-paste losing characters.","solutions":["Validate the string with JSON.parse in a scratch script or a JSON linter before feeding it to parseAudioFxChain.","Regenerate the chain via serializeAudioFxChain(chain) rather than hand-writing JSON.","If editing by hand, fix the cited syntax error in the interpolated message (it names the position)."],"exampleFix":"// before\nconst chain = parseAudioFxChain('{ \"version\": 1, \"nodes\": [ }'); // trailing comma / missing object\n\n// after — produce the JSON programmatically\nimport { serializeAudioFxChain } from \"@hyperframes/core\";\nconst attr = serializeAudioFxChain({ version: 1, nodes: [{ type: \"highpass\", params: { frequency: 1000 } }] });\nconst chain = parseAudioFxChain(attr);","handlingStrategy":"try-catch","validationCode":"function isValidChainJson(s: string): boolean {\n  try { JSON.parse(s); return true; } catch { return false; }\n}\nif (!isValidChainJson(attr)) throw new Error('chain attribute is not valid JSON');","typeGuard":"function isValidChainJson(s: string): boolean { try { JSON.parse(s); return true; } catch { return false; } }","tryCatchPattern":"import { parseAudioFxChain, AudioFxChainError } from \"@hyperframes/core\";\ntry { chain = parseAudioFxChain(json); }\ncatch (err) {\n  if (err instanceof AudioFxChainError) { /* show err.message, regenerate via serializeAudioFxChain */ }\n  else throw err;\n}","preventionTips":["Generate chain JSON with serializeAudioFxChain; never hand-write it.","Lint chain JSON with a JSON parser before rendering.","Avoid build steps that escape/truncate the data-fx-chain attribute."],"tags":["json","parsing","audio-fx-chain","validation"],"backgroundTag":null,"analyzedSha":"c2996c8626135db5253519359d8a063d3bafad8d","analyzedAt":"2026-08-12T22:18:56.877Z","schemaVersion":2},"datasetVersion":"2026-08-12T23:17:12.415Z"}