{"record":{"id":"dc2b3eac9c775da2","repo":"Yeachan-Heo/oh-my-codex","slug":"question-input-must-be-a-json-object","errorCode":null,"errorMessage":"question input must be a JSON object","messagePattern":"question input must be a JSON object","errorType":"validation","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"src/question/types.ts","lineNumber":185,"sourceCode":"  return {\n    id,\n    ...(header ? { header } : {}),\n    question,\n    options: rawOptions.map((option, optionIndex) => normalizeOption(option, optionIndex)),\n    allow_other,\n    other_label,\n    multi_select: type === 'multi-answerable',\n    type,\n  };\n}\n\nfunction normalizeLegacyQuestion(raw: Record<string, unknown>, header?: string): NormalizedQuestionItem {\n  return normalizeQuestionItem({ ...raw, id: safeString(raw.id).trim() || 'q-1', header }, 0, header);\n}\n\nexport function normalizeQuestionInput(raw: unknown): QuestionInput {\n  if (!raw || typeof raw !== 'object' || Array.isArray(raw)) {\n    throw new Error('question input must be a JSON object');\n  }\n\n  const input = raw as Record<string, unknown>;\n  const header = safeString(input.header).trim() || undefined;\n  const source = safeString(input.source).trim() || undefined;\n  const session_id = safeString(input.session_id).trim() || undefined;\n  const rawQuestions = Array.isArray(input.questions) ? input.questions : undefined;\n\n  const questions = rawQuestions\n    ? rawQuestions.map((item, index) => normalizeQuestionItem(item, index, header))\n    : [normalizeLegacyQuestion(input, header)];\n\n  if (questions.length === 0) throw new Error('questions must be a non-empty array');\n  const seenIds = new Set<string>();\n  for (const question of questions) {\n    if (seenIds.has(question.id)) throw new Error(`questions id must be unique: ${question.id}`);\n    seenIds.add(question.id);\n  }","sourceCodeStart":167,"sourceCodeEnd":203,"githubUrl":"https://github.com/Yeachan-Heo/oh-my-codex/blob/3ad79a8a6fe6e95fdbb8c00e40716fffe4011ce2/src/question/types.ts#L167-L203","documentation":"normalizeQuestionInput requires the top-level question input to be a plain JSON object. Arrays, strings, numbers, null, or primitives are rejected because the normalizer expects an object with fields like header, source, questions, etc.","triggerScenarios":"Calling normalizeQuestionInput (or an API that normalizes input, e.g. record builders) with a JSON array of questions, a JSON string, or a null/undefined value.","commonSituations":"Passing JSON.parse output that is an array of question objects; reading a file whose root is an array; passing a stringified JSON instead of a parsed object.","solutions":["Wrap arrays in an object: { questions: [...] } instead of a bare array","Ensure you JSON.parse the payload before passing it","Check the value is non-null and typeof === 'object' before calling"],"exampleFix":"// before\nnormalizeQuestionInput([{ id: 'q1' }]);\n// after\nnormalizeQuestionInput({ questions: [{ id: 'q1' }] });","handlingStrategy":"type-guard","validationCode":"if (!raw || typeof raw !== 'object' || Array.isArray(raw)) throw new TypeError('expected a question object');","typeGuard":"const isQuestionObject = (v: unknown): v is Record<string, unknown> =>\n  Boolean(v) && typeof v === 'object' && !Array.isArray(v);","tryCatchPattern":null,"preventionTips":["Always JSON.parse before passing","Wrap arrays as { questions: [...] }"],"tags":["input-validation","json","question-schema"],"backgroundTag":"invalid-input-shape","analyzedSha":"3ad79a8a6fe6e95fdbb8c00e40716fffe4011ce2","analyzedAt":"2026-08-27T22:18:39.783Z","schemaVersion":2},"datasetVersion":"2026-08-28T00:17:15.603Z"}