composer/composer · error · InvalidArgumentException

The json file must be an object ({})

Error message

The json file must be an object ({})

What it means

Thrown by the JsonManipulator constructor when the provided JSON contents, after trimming, do not begin with '{' and end with '}'. JsonManipulator performs surgical regex edits on a JSON *object*; a JSON array, scalar, or malformed content cannot be manipulated, so it rejects non-object input.

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 6ffc117740)

Solutions

  1. Ensure the contents passed to JsonManipulator are a full JSON object (`{ ... }`), typically the entire composer.json.
  2. If you have a JSON array, wrap it in an object before manipulating, or edit it with json_decode/json_encode instead.
  3. Re-fetch the file contents and confirm it is the root composer.json, not a sub-document.
  4. Validate with `json_decode($contents)` returning an object before constructing the manipulator.

Example fix

// before
$contents = file_get_contents('packages.json'); // an array [...]
$m = new JsonManipulator($contents); // throws

// after
$contents = file_get_contents('composer.json'); // an object {...}
$m = new JsonManipulator($contents);
Defensive patterns

Strategy: validation

Validate before calling

$decoded = json_decode(trim($contents));
if (!($decoded instanceof \stdClass)) {
    throw new \InvalidArgumentException('Contents must be a JSON object ({...}), got '.gettype($decoded));
}
$manipulator = new \Composer\Json\JsonManipulator($contents);

Type guard

function isJsonObjectContents(string $contents): bool {
    $decoded = json_decode(trim($contents));
    return $decoded instanceof \stdClass;
}

Try / catch

try {
    $m = new \Composer\Json\JsonManipulator($contents);
} catch (\InvalidArgumentException $e) {
    if (str_contains($e->getMessage(), 'must be an object')) {
        // load the correct object-shaped file (e.g. composer.json) and retry
    }
    throw $e;
}

Prevention

When it happens

Trigger: Constructing `new JsonManipulator($contents)` where $contents is a JSON array (`[...]`), a scalar, empty-after-trim (handled separately as '{}'), or a fragment that is not a top-level object.

Common situations: Passing the body of a 'repositories' array directly instead of the whole composer.json; loading the wrong file (e.g. an array-shaped fixture); corrupted/partial JSON; programmatic callers that assume array input.

Related errors


AI-assisted analysis of composer/composer@6ffc117740 (2026-08-07). Data as JSON: /api/errors/5fe2125d23f00922. Report an issue: GitHub.