dmtrKovalenko/fff · error · Error
expected a JSON object
Error message
expected a JSON object
What it means
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".
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.
Example fix
// before (config.json)
[
{ "keymaps": { "open": "<leader>ff" } }
]
// after
{
"keymaps": { "open": "<leader>ff" }
} Defensive patterns
Strategy: validation
Validate before calling
const parsed = JSON.parse(fs.readFileSync(configPath, 'utf8'));
if (parsed === null || typeof parsed !== 'object' || Array.isArray(parsed)) {
throw new Error(`${configPath}: expected a JSON object at the top level`);
} Type guard
function isRecord(v: unknown): v is Record<string, unknown> {
return typeof v === 'object' && v !== null && !Array.isArray(v);
} Try / catch
try {
cfg = config(configPath);
} catch (e) {
if (/expected a JSON object/.test(e.message)) {
console.error(`${configPath} must contain a { ... } object; using defaults`);
cfg = defaultConfig;
} else throw e;
} Prevention
- 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.
When it happens
Trigger: 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.
Common situations: 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.
Related errors
- unknown option
- not valid JSON ( )
- Could not read pi-fff config at
- patterns array must have at least 1 element
- Path constraint must be relative to the workspace
AI-assisted analysis of dmtrKovalenko/fff@7f8537e70f (2026-09-10).
Data as JSON: /api/errors/ec3b5d81433bb4d9.
Report an issue: GitHub.
Appendix: source
Thrown at packages/pi-fff/src/config.ts:53
try {
contents = readFileSync(configPath, "utf8");
} catch (error: unknown) {
if ((error as NodeJS.ErrnoException).code === "ENOENT") return {};
throw new Error(
`Could not read pi-fff config at ${configPath}: ${errorMessage(error)}`,
);
}
let parsed: unknown;
try {
parsed = JSON.parse(contents);
} catch (error: unknown) {
throw invalidConfig(configPath, `not valid JSON (${errorMessage(error)})`);
}
if (!isRecord(parsed)) {
throw invalidConfig(configPath, "expected a JSON object");
}
for (const key of Object.keys(parsed)) {
if (!CONFIG_KEYS.has(key as keyof FffConfig)) {
throw invalidConfig(configPath, `unknown option "${key}"`);
}
}
if (parsed.mode !== undefined && !VALID_MODES.includes(parsed.mode as FffMode)) {
throw invalidConfig(configPath, `"mode" must be one of ${VALID_MODES.join(", ")}`);
}
validateString(configPath, parsed, "$schema");
validateString(configPath, parsed, "frecencyDbPath");
validateString(configPath, parsed, "historyDbPath");
validateBoolean(configPath, parsed, "enableFsRootScanning");
validateBoolean(configPath, parsed, "enableHomeDirScanning");
validateBoolean(configPath, parsed, "warnOnHomeDirScan");View on GitHub (pinned to 7f8537e70f)