{"record":{"id":"6cd27ceffe1434ec","repo":"heygen-com/hyperframes","slug":"node-i-is-not-an-object","errorCode":null,"errorMessage":"Node ${i} is not an object.","messagePattern":"Node (.+?) is not an object\\.","errorType":"validation","errorClass":"AudioFxChainError","httpStatus":null,"severity":"error","filePath":"packages/core/src/audioFx.ts","lineNumber":779,"sourceCode":"  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)}`);\n    }\n    return {\n      type: node.type,\n      enabled: node.enabled !== false,\n      params: normalizeAudioFxParams(\n        node.type,\n        (node.params ?? undefined) as HfAudioFxParamValues | undefined,\n      ),\n    };\n  });\n  return { version: HF_AUDIO_FX_CHAIN_VERSION, nodes };\n}\n\n/** The nodes that should process audio, in order. */","sourceCodeStart":761,"sourceCodeEnd":797,"githubUrl":"https://github.com/heygen-com/hyperframes/blob/c2996c8626135db5253519359d8a063d3bafad8d/packages/core/src/audioFx.ts#L761-L797","documentation":"While mapping over obj.nodes, parseAudioFxChain() checks each element is a non-null object. Any array element that is null, a number, a string, a boolean, or an array is rejected, with the element index interpolated so you can locate it. This runs before any field is read off the node.","triggerScenarios":"A nodes array containing a null placeholder (e.g. trailing comma producing a hole, or JSON null), a bare string id, or a nested array — e.g. [{ type: 'highpass' }, null, { type: 'reverb' }].","commonSituations":"Hand-editing that leaves a null; a serialization bug that emitted null for a deleted node; copy-paste artifacts; JSON like [\"highpass\",\"reverb\"] instead of objects.","solutions":["Make every element of the nodes array a JSON object ({ type, ...params }).","Remove null/placeholder entries before serializing.","Regenerate the chain via serializeAudioFxChain."],"exampleFix":"// before\nparseAudioFxChain('{ \"version\": 1, \"nodes\": [ {\"type\":\"highpass\"}, null ] }'); // Node 1 is not an object\n\n// after\nparseAudioFxChain('{ \"version\": 1, \"nodes\": [ {\"type\":\"highpass\"} ] }');","handlingStrategy":"validation","validationCode":"const parsed = JSON.parse(json);\nconst badIndex = parsed.nodes.findIndex((n: unknown) => typeof n !== \"object\" || n === null);\nif (badIndex !== -1) throw new Error(`node ${badIndex} is not an object`);","typeGuard":"function isPlainObject(v: unknown): v is Record<string, unknown> {\n  return typeof v === \"object\" && v !== null && !Array.isArray(v);\n}","tryCatchPattern":"try { parseAudioFxChain(json); }\ncatch (err) { if (/is not an object/.test(String(err))) { /* drop/fix null entries */ } else throw err; }","preventionTips":["Strip null/placeholder entries before serializing.","Make every node a { type, params } object; use serializeAudioFxChain."],"tags":["parsing","audio-fx-chain","validation","schema"],"backgroundTag":null,"analyzedSha":"c2996c8626135db5253519359d8a063d3bafad8d","analyzedAt":"2026-08-12T22:18:56.877Z","schemaVersion":2},"datasetVersion":"2026-08-12T23:17:12.415Z"}