composer/composer · error · InvalidArgumentException
The json file must be an object
Error message
The json file must be an object ({}) What it means
JsonManipulator's constructor throws an InvalidArgumentException unless the (trimmed) contents match '^\{.*\}$' (a single JSON object). JsonManipulator performs regex-based surgical edits on composer.json and assumes an object root; arrays, scalars, or garbage would break every edit method, so it refuses upfront. Empty input is normalized to '{}' first and does not throw.
Solutions
- Ensure the input is a valid JSON object — wrap arrays/scalars in an object, or use JsonFile instead for non-object documents.
- Repair corrupted composer.json: restore from git (`git checkout -- composer.json`) or re-init (`composer init`).
- If you are processing a non-object JSON file, parse/modify/encode with json_decode+json_encode rather than JsonManipulator.
Example fix
// before
$manipulator = new JsonManipulator('["a","b"]'); // array root
// The json file must be an object ({})
// after
$manipulator = new JsonManipulator('{"items":["a","b"]}'); // object root Defensive patterns
Strategy: validation
Validate before calling
$trimmed = trim($contents);
if ($trimmed === '') { $trimmed = '{}'; }
if (!preg_match('#^\{.*\}$#s', $trimmed)) {
throw new \RuntimeException('JsonManipulator requires a JSON object root; got: ' . substr($trimmed, 0, 60));
}
return new \Composer\Json\JsonManipulator($trimmed); Type guard
function isJsonObjectRoot(string $contents): bool {
$c = trim($contents);
if ($c === '') return true; // normalized to '{}'
return (bool) preg_match('#^\{.*\}$#s', $c);
} Try / catch
try {
$m = new JsonManipulator($contents);
} catch (\InvalidArgumentException $e) {
if ($e->getMessage() === 'The json file must be an object ({})') {
// fall back to decode/modify/encode for non-object JSON documents
$data = json_decode($contents, true);
// ...mutate $data...
$out = json_encode($data, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE);
} else {
throw $e;
}
} Prevention
- Only feed JsonManipulator composer.json-style object documents; use json_decode/encode for arrays.
- Validate the document root with the type guard above before constructing.
- Keep composer.json well-formed and version-controlled so corrupt input is rare.
When it happens
Trigger: Constructing `new JsonManipulator($contents)` where $contents is a JSON array, a bare scalar, or malformed text rather than a top-level JSON object. Reached by `composer config`, plugin installers, and tooling that manipulate composer.json textually.
Common situations: Passing the contents of a file that is actually a JSON array (e.g. an installed.json fragment, a packages.json) into JsonManipulator; feeding it decoded-then-stringified non-object data; corrupt or truncated composer.json.
Related errors
- Cannot merge array and object for
- exists and is not a directory.
- Expected an array or object for
- Invalid channel , must be one of
- Invalid ignore-id entry at index
AI-assisted analysis of composer/composer@c435d285c9 (2026-08-07).
Data as JSON: /api/errors/5fe2125d23f00922.
Report an issue: GitHub.
Appendix: source
Thrown at src/Composer/Json/JsonManipulator.php:48
(?<object> \{ (?: (?&pair) (?: , (?&pair) )*+ )?+ \s*+ \} )
(?<json> \s*+ (?: (?&number) | (?&boolean) | (?&string) | (?&array) | (?&object) ) )
)';
/** @var string */
private $contents;
/** @var string */
private $newline;
/** @var string */
private $indent;
public function __construct(string $contents)
{
$contents = trim($contents);
if ($contents === '') {
$contents = '{}';
}
if (!Preg::isMatch('#^\{(.*)\}$#s', $contents)) {
throw new \InvalidArgumentException('The json file must be an object ({})');
}
$this->newline = false !== strpos($contents, "\r\n") ? "\r\n" : "\n";
$this->contents = $contents === '{}' ? '{' . $this->newline . '}' : $contents;
$this->detectIndenting();
}
public function getContents(): string
{
return $this->contents . $this->newline;
}
public function addLink(string $type, string $package, string $constraint, bool $sortPackages = false): bool
{
$decoded = JsonFile::parseJson($this->contents);
// no link of that type yet
if (!isset($decoded[$type])) {
return $this->addMainKey($type, [$package => $constraint]);View on GitHub (pinned to c435d285c9)