{"record":{"id":"5fe2125d23f00922","repo":"composer/composer","slug":"the-json-file-must-be-an-object","errorCode":null,"errorMessage":"The json file must be an object ({})","messagePattern":"The json file must be an object \\((.+?)\\)","errorType":"exception","errorClass":"InvalidArgumentException","httpStatus":null,"severity":"error","filePath":"src/Composer/Json/JsonManipulator.php","lineNumber":48,"sourceCode":"       (?<object>    \\{  (?:  (?&pair)  (?: , (?&pair)  )*+  )?+  \\s*+ \\} )\n       (?<json>      \\s*+ (?: (?&number) | (?&boolean) | (?&string) | (?&array) | (?&object) ) )\n    )';\n\n    /** @var string */\n    private $contents;\n    /** @var string */\n    private $newline;\n    /** @var string */\n    private $indent;\n\n    public function __construct(string $contents)\n    {\n        $contents = trim($contents);\n        if ($contents === '') {\n            $contents = '{}';\n        }\n        if (!Preg::isMatch('#^\\{(.*)\\}$#s', $contents)) {\n            throw new \\InvalidArgumentException('The json file must be an object ({})');\n        }\n        $this->newline = false !== strpos($contents, \"\\r\\n\") ? \"\\r\\n\" : \"\\n\";\n        $this->contents = $contents === '{}' ? '{' . $this->newline . '}' : $contents;\n        $this->detectIndenting();\n    }\n\n    public function getContents(): string\n    {\n        return $this->contents . $this->newline;\n    }\n\n    public function addLink(string $type, string $package, string $constraint, bool $sortPackages = false): bool\n    {\n        $decoded = JsonFile::parseJson($this->contents);\n\n        // no link of that type yet\n        if (!isset($decoded[$type])) {\n            return $this->addMainKey($type, [$package => $constraint]);","sourceCodeStart":30,"sourceCodeEnd":66,"githubUrl":"https://github.com/composer/composer/blob/c435d285c9120efdca35696769c72ea9fdcc0466/src/Composer/Json/JsonManipulator.php#L30-L66","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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."],"exampleFix":"// before\n$manipulator = new JsonManipulator('[\"a\",\"b\"]'); // array root\n// The json file must be an object ({})\n\n// after\n$manipulator = new JsonManipulator('{\"items\":[\"a\",\"b\"]}'); // object root","handlingStrategy":"validation","validationCode":"$trimmed = trim($contents);\nif ($trimmed === '') { $trimmed = '{}'; }\nif (!preg_match('#^\\{.*\\}$#s', $trimmed)) {\n    throw new \\RuntimeException('JsonManipulator requires a JSON object root; got: ' . substr($trimmed, 0, 60));\n}\nreturn new \\Composer\\Json\\JsonManipulator($trimmed);","typeGuard":"function isJsonObjectRoot(string $contents): bool {\n    $c = trim($contents);\n    if ($c === '') return true; // normalized to '{}'\n    return (bool) preg_match('#^\\{.*\\}$#s', $c);\n}","tryCatchPattern":"try {\n    $m = new JsonManipulator($contents);\n} catch (\\InvalidArgumentException $e) {\n    if ($e->getMessage() === 'The json file must be an object ({})') {\n        // fall back to decode/modify/encode for non-object JSON documents\n        $data = json_decode($contents, true);\n        // ...mutate $data...\n        $out = json_encode($data, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE);\n    } else {\n        throw $e;\n    }\n}","preventionTips":["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."],"tags":["json","json-manipulator","validation","configuration"],"backgroundTag":null,"analyzedSha":"c435d285c9120efdca35696769c72ea9fdcc0466","analyzedAt":"2026-08-07T18:58:23.525Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}