{"record":{"id":"f2399ffc5afc3627","repo":"OrchardCMS/OrchardCore","slug":"top-level-json-element-must-be-an-object-instead-doc","errorCode":null,"errorMessage":"Top-level JSON element must be an object. Instead, '{doc.RootElement.ValueKind}' was found.","messagePattern":"Top-level JSON element must be an object\\. Instead, '(.+?)' was found\\.","errorType":"exception","errorClass":"FormatException","httpStatus":null,"severity":"error","filePath":"src/OrchardCore/OrchardCore.Abstractions/Shell/Configuration/Internal/JsonConfigurationParser.cs","lineNumber":33,"sourceCode":"\n    public static IDictionary<string, string?> Parse(Stream utf8Json)\n        => new JsonConfigurationParser().ParseStream(utf8Json);\n\n    public static IDictionary<string, string?> Parse(string document)\n        => new JsonConfigurationParser().ParseDocument(document);\n\n    public static Task<IDictionary<string, string?>> ParseAsync(Stream utf8Json)\n        => new JsonConfigurationParser().ParseStreamAsync(utf8Json);\n\n    private Dictionary<string, string?> ParseStream(Stream utf8Json)\n    {\n        try\n        {\n            using (var doc = JsonDocument.Parse(utf8Json, JOptions.Document))\n            {\n                if (doc.RootElement.ValueKind != JsonValueKind.Object)\n                {\n                    throw new FormatException($\"Top-level JSON element must be an object. Instead, '{doc.RootElement.ValueKind}' was found.\");\n                }\n\n                VisitObjectElement(doc.RootElement);\n            }\n\n            return _data;\n        }\n        catch (JsonException e)\n        {\n            throw new FormatException(\"Could not parse the JSON document.\", e);\n        }\n    }\n\n    private Dictionary<string, string?> ParseDocument(string document)\n    {\n        try\n        {\n            using (var doc = JsonDocument.Parse(document, JOptions.Document))","sourceCodeStart":15,"sourceCodeEnd":51,"githubUrl":"https://github.com/OrchardCMS/OrchardCore/blob/4306c0717fe573f6fca1b4955909ddab6a192807/src/OrchardCore/OrchardCore.Abstractions/Shell/Configuration/Internal/JsonConfigurationParser.cs#L15-L51","documentation":"JsonConfigurationParser.Parse(Stream) flattens a JSON appsettings-like document into configuration key/value pairs, and it requires the top-level JSON element to be an object ({}) because keys are built from property paths. If the root is an array, string, number, or any other JSON value kind, a FormatException is thrown naming the actual ValueKind found.","triggerScenarios":"Calling JsonConfigurationParser.Parse(Stream) with a stream whose JSON root is not an object, e.g. a stream containing '[1,2,3]', '\"text\"', '42', 'true', or 'null'. Also occurs when a tenant's appsettings file was accidentally overwritten with a JSON array or scalar.","commonSituations":"A shell appsettings.json (e.g. App_Data/Sites/{tenant}/appsettings.json) truncated or replaced with a non-object payload; a tool exporting settings as a JSON array; someone piping the wrong file (e.g. a JSON log or a package.json 'dependencies' fragment) into configuration parsing.","solutions":["Wrap the document's top level in a JSON object, e.g. change '[1,2,3]' to '{\"values\": [1,2,3]}'","Inspect the file/stream contents and restore a valid appsettings object with key:value properties","Validate the JSON root before parsing: parse with System.Text.Json and check JsonElement.ValueKind == JsonValueKind.Object","If parsing tenant appsettings, restore the file from source control or re-save tenant settings through the admin UI"],"exampleFix":"// before\n[\"Default\", \"MyTenant\"]\n// after\n{\"tenants\": [\"Default\", \"MyTenant\"]}","handlingStrategy":"validation","validationCode":"using var doc = JsonDocument.Parse(stream, JOptions.Document);\nif (doc.RootElement.ValueKind != JsonValueKind.Object)\n    throw new InvalidDataException($\"Expected a JSON object, got '{doc.RootElement.ValueKind}'.\");\nstream.Position = 0; // rewind before the real parse\nvar data = JsonConfigurationParser.Parse(stream);","typeGuard":"static bool IsJsonObject(Stream s) { using var doc = JsonDocument.Parse(s, JOptions.Document); return doc.RootElement.ValueKind == JsonValueKind.Object; }","tryCatchPattern":"try { JsonConfigurationParser.Parse(stream); }\ncatch (FormatException ex) { log.LogError(ex, \"JSON root must be an object\"); }","preventionTips":["Check the first non-whitespace character of the payload is '{'","Keep appsettings files as objects, never arrays or scalars","Validate JSON after any scripted write to config files","Back up appsettings.json before tooling edits"],"tags":["json","configuration","format"],"backgroundTag":"invalid-config-value","analyzedSha":"4306c0717fe573f6fca1b4955909ddab6a192807","analyzedAt":"2026-09-13T17:41:05.024Z","contentChangedAt":"2026-09-13T17:41:05.024Z","schemaVersion":2},"datasetVersion":"2026-09-15T23:17:13.987Z"}