OrchardCMS/OrchardCore · error · FormatException

Top-level JSON element must be an object. Instead

Error message

Top-level JSON element must be an object. Instead, '{doc.RootElement.ValueKind}' was found.

What it means

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.

Solutions

  1. Wrap the document's top level in a JSON object, e.g. change '[1,2,3]' to '{"values": [1,2,3]}'
  2. Inspect the file/stream contents and restore a valid appsettings object with key:value properties
  3. Validate the JSON root before parsing: parse with System.Text.Json and check JsonElement.ValueKind == JsonValueKind.Object
  4. If parsing tenant appsettings, restore the file from source control or re-save tenant settings through the admin UI

Example fix

// before
["Default", "MyTenant"]
// after
{"tenants": ["Default", "MyTenant"]}
Defensive patterns

Strategy: validation

Validate before calling

using var doc = JsonDocument.Parse(stream, JOptions.Document);
if (doc.RootElement.ValueKind != JsonValueKind.Object)
    throw new InvalidDataException($"Expected a JSON object, got '{doc.RootElement.ValueKind}'.");
stream.Position = 0; // rewind before the real parse
var data = JsonConfigurationParser.Parse(stream);

Type guard

static bool IsJsonObject(Stream s) { using var doc = JsonDocument.Parse(s, JOptions.Document); return doc.RootElement.ValueKind == JsonValueKind.Object; }

Try / catch

try { JsonConfigurationParser.Parse(stream); }
catch (FormatException ex) { log.LogError(ex, "JSON root must be an object"); }

Prevention

When it happens

Trigger: 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.

Common situations: 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.

Understand the failure class

Background: "Invalid value" and "allowed values are" config errors: what your library rejected and how to fix it — this error's family across 41 libraries.

Related errors


AI-assisted analysis of OrchardCMS/OrchardCore@4306c0717f (2026-09-13). Data as JSON: /api/errors/f2399ffc5afc3627. Report an issue: GitHub.

Appendix: source

Thrown at src/OrchardCore/OrchardCore.Abstractions/Shell/Configuration/Internal/JsonConfigurationParser.cs:33

    public static IDictionary<string, string?> Parse(Stream utf8Json)
        => new JsonConfigurationParser().ParseStream(utf8Json);

    public static IDictionary<string, string?> Parse(string document)
        => new JsonConfigurationParser().ParseDocument(document);

    public static Task<IDictionary<string, string?>> ParseAsync(Stream utf8Json)
        => new JsonConfigurationParser().ParseStreamAsync(utf8Json);

    private Dictionary<string, string?> ParseStream(Stream utf8Json)
    {
        try
        {
            using (var doc = JsonDocument.Parse(utf8Json, JOptions.Document))
            {
                if (doc.RootElement.ValueKind != JsonValueKind.Object)
                {
                    throw new FormatException($"Top-level JSON element must be an object. Instead, '{doc.RootElement.ValueKind}' was found.");
                }

                VisitObjectElement(doc.RootElement);
            }

            return _data;
        }
        catch (JsonException e)
        {
            throw new FormatException("Could not parse the JSON document.", e);
        }
    }

    private Dictionary<string, string?> ParseDocument(string document)
    {
        try
        {
            using (var doc = JsonDocument.Parse(document, JOptions.Document))

View on GitHub (pinned to 4306c0717f)