affaan-m/ECC · error

Invalid at : expected a JSON object

Error message

Invalid ${label} at ${filePath}: expected a JSON object

What it means

After successfully parsing the file, readJsonObject in the Cursor project target additionally requires the top-level value to be a plain JSON object — not null, not an array. Config files this installer merges into must be objects so keys can be merged; anything else is rejected with the file path in the message.

Solutions

  1. Open the file at filePath in the message and change the top-level value to a JSON object `{ ... }`
  2. If the file is an empty placeholder, replace `[]` or `null` with `{}`
  3. Re-run the install so the merge operation can apply on the now-object-shaped file

Example fix

// before
[]
// after
{}
Defensive patterns

Strategy: type-guard

Validate before calling

// Pre-check the root shape before install
const parsed = JSON.parse(fs.readFileSync(cursorConfigPath, 'utf8'));
if (parsed === null || Array.isArray(parsed) || typeof parsed !== 'object') {
  console.error(`${cursorConfigPath} must be a JSON object at the top level`);
}

Type guard

function isJsonObject(v) {
  return v !== null && typeof v === 'object' && !Array.isArray(v);
}

Try / catch

try {
  runInstallOperation(op);
} catch (e) {
  if (/Invalid .* at .*: expected a JSON object/.test(e.message)) {
    // rewrite the file's root as an object and retry
  } else throw e;
}

Prevention

When it happens

Trigger: createJsonMergeOperation reads a destination file whose contents parse as JSON but whose root is an array (e.g. `[]` or `[...]`), a bare string/number, or the literal `null`.

Common situations: Someone reset a Cursor config file to `[]` assuming it is a list format; a tool exported settings as a JSON array; a script wrote `null` as a placeholder.

Related errors


AI-assisted analysis of affaan-m/ECC@8321021c54 (2026-09-16). Data as JSON: /api/errors/3dce563a1ccb6498. Report an issue: GitHub.

Appendix: source

Thrown at scripts/lib/install-targets/cursor-project.js:32

  if (path.basename(sourceRelativeFile).toLowerCase() === 'readme.md') {
    return null;
  }

  return fileName.endsWith('.md')
    ? `${fileName.slice(0, -3)}.mdc`
    : fileName;
}

function readJsonObject(filePath, label) {
  let parsed;
  try {
    parsed = JSON.parse(fs.readFileSync(filePath, 'utf8'));
  } catch (error) {
    throw new Error(`Failed to parse ${label} at ${filePath}: ${error.message}`);
  }

  if (!parsed || typeof parsed !== 'object' || Array.isArray(parsed)) {
    throw new Error(`Invalid ${label} at ${filePath}: expected a JSON object`);
  }

  return parsed;
}

function createJsonMergeOperation({ moduleId, repoRoot, sourceRelativePath, destinationPath }) {
  const sourcePath = path.join(repoRoot, sourceRelativePath);
  if (!fs.existsSync(sourcePath) || !fs.statSync(sourcePath).isFile()) {
    return null;
  }

  return createManagedOperation({
    kind: 'merge-json',
    moduleId,
    sourceRelativePath,
    destinationPath,
    strategy: 'merge-json',
    ownership: 'managed',

View on GitHub (pinned to 8321021c54)