{"record":{"id":"ec3b5d81433bb4d9","repo":"dmtrKovalenko/fff","slug":"expected-a-json-object","errorCode":null,"errorMessage":"expected a JSON object","messagePattern":"expected a JSON object","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"packages/pi-fff/src/config.ts","lineNumber":53,"sourceCode":"\n  try {\n    contents = readFileSync(configPath, \"utf8\");\n  } catch (error: unknown) {\n    if ((error as NodeJS.ErrnoException).code === \"ENOENT\") return {};\n    throw new Error(\n      `Could not read pi-fff config at ${configPath}: ${errorMessage(error)}`,\n    );\n  }\n\n  let parsed: unknown;\n  try {\n    parsed = JSON.parse(contents);\n  } catch (error: unknown) {\n    throw invalidConfig(configPath, `not valid JSON (${errorMessage(error)})`);\n  }\n\n  if (!isRecord(parsed)) {\n    throw invalidConfig(configPath, \"expected a JSON object\");\n  }\n\n  for (const key of Object.keys(parsed)) {\n    if (!CONFIG_KEYS.has(key as keyof FffConfig)) {\n      throw invalidConfig(configPath, `unknown option \"${key}\"`);\n    }\n  }\n\n  if (parsed.mode !== undefined && !VALID_MODES.includes(parsed.mode as FffMode)) {\n    throw invalidConfig(configPath, `\"mode\" must be one of ${VALID_MODES.join(\", \")}`);\n  }\n\n  validateString(configPath, parsed, \"$schema\");\n  validateString(configPath, parsed, \"frecencyDbPath\");\n  validateString(configPath, parsed, \"historyDbPath\");\n  validateBoolean(configPath, parsed, \"enableFsRootScanning\");\n  validateBoolean(configPath, parsed, \"enableHomeDirScanning\");\n  validateBoolean(configPath, parsed, \"warnOnHomeDirScan\");","sourceCodeStart":35,"sourceCodeEnd":71,"githubUrl":"https://github.com/dmtrKovalenko/fff/blob/7f8537e70f0ea1210f9acbbfc4640141105cdc78/packages/pi-fff/src/config.ts#L35-L71","documentation":"The config file parsed as JSON but the top-level value is not a JSON object (e.g. an array, string, number, or null). FffConfig requires an object whose keys are known option names, so loadConfig rejects any other top-level shape with \"expected a JSON object\".","triggerScenarios":"Config file containing just a JSON array ([...]), a bare string/number, or the literal null; a script that serialized the wrong value into the config path; concatenating config fragments into an array.","commonSituations":"Overwriting the config with a settings array from another tool; an export script doing JSON.stringify(items) into the config; a user writing \"my settings\" (a bare string) into the file by accident.","solutions":["Wrap the settings in a top-level object: { \"option\": value, ... }.","Check what wrote the file — fix the writer to serialize an object, not an array or scalar.","Restore or recreate the config file with the documented schema (keys from CONFIG_KEYS).","Add a schema check in your tooling that fails when the parsed root is not an object."],"exampleFix":"// before (config.json)\n[\n  { \"keymaps\": { \"open\": \"<leader>ff\" } }\n]\n// after\n{\n  \"keymaps\": { \"open\": \"<leader>ff\" }\n}","handlingStrategy":"validation","validationCode":"const parsed = JSON.parse(fs.readFileSync(configPath, 'utf8'));\nif (parsed === null || typeof parsed !== 'object' || Array.isArray(parsed)) {\n  throw new Error(`${configPath}: expected a JSON object at the top level`);\n}","typeGuard":"function isRecord(v: unknown): v is Record<string, unknown> {\n  return typeof v === 'object' && v !== null && !Array.isArray(v);\n}","tryCatchPattern":"try {\n  cfg = config(configPath);\n} catch (e) {\n  if (/expected a JSON object/.test(e.message)) {\n    console.error(`${configPath} must contain a { ... } object; using defaults`);\n    cfg = defaultConfig;\n  } else throw e;\n}","preventionTips":["Always serialize config as a top-level object, never an array or scalar.","Add a JSON schema check (root type: object) in CI.","Guard any script that writes the config to serialize the right shape.","Document the config schema next to the file so hand edits stay valid."],"tags":["config","json","validation"],"backgroundTag":"config-type-mismatch","analyzedSha":"7f8537e70f0ea1210f9acbbfc4640141105cdc78","analyzedAt":"2026-09-10T07:26:53.407Z","contentChangedAt":"2026-09-10T07:26:53.407Z","schemaVersion":2},"datasetVersion":"2026-09-16T09:17:16.951Z"}