tui-cs/Terminal.Gui · error · JsonException

Expected a JSON array ("[ {{ ... }} ]"), but got "{reader.To

Error message

Expected a JSON array ("[ {{ ... }} ]"), but got "{reader.TokenType}".

What it means

Thrown by ConcurrentDictionaryJsonConverter<T>.Read: Terminal.Gui serializes its ConcurrentDictionary<string,T> properties (e.g. Themes) as a JSON ARRAY of single-key objects ([ {"key": value}, ... ]), not as a JSON object. If the reader's first token is not StartArray, this fires.

Source

Thrown at Terminal.Gui/Configuration/ConcurrentDictionaryJsonConverter.cs:18

#nullable disable
using System.Collections.Concurrent;
using System.Text.Json;
using System.Text.Json.Serialization;

namespace Terminal.Gui.Configuration;

internal class ConcurrentDictionaryJsonConverter<T> : JsonConverter<ConcurrentDictionary<string, T>>
{
    public override ConcurrentDictionary<string, T> Read (
        ref Utf8JsonReader reader,
        Type typeToConvert,
        JsonSerializerOptions options
    )
    {
        if (reader.TokenType != JsonTokenType.StartArray)
        {
            throw new JsonException ($"Expected a JSON array (\"[ {{ ... }} ]\"), but got \"{reader.TokenType}\".");
        }

        // If the Json options indicate ignoring case, use the invariant culture ignore case comparer
        ConcurrentDictionary<string, T> dictionary = new (
                                                          options.PropertyNameCaseInsensitive
                                                              ? StringComparer.InvariantCultureIgnoreCase
                                                              : StringComparer.InvariantCulture);

        while (reader.Read ())
        {
            if (reader.TokenType == JsonTokenType.StartObject)
            {
                reader.Read ();

                if (reader.TokenType == JsonTokenType.PropertyName)
                {
                    string key = reader.GetString ();
                    reader.Read ();

View on GitHub (pinned to 2e47b11478)

Solutions

  1. Wrap each key/value pair as its own object inside an array: [ {"ThemeName": { ...scheme... } } ].
  2. Export a config from ConfigurationManager once to see the exact array shape it expects, then mirror it.
  3. Use a JSON schema for the config to enforce the array shape.

Example fix

// before
"Themes": { "Default": { ... } }
// after
"Themes": [ { "Default": { ... } } ]
Defensive patterns

Strategy: validation

Validate before calling

// Themes must be a JSON array of single-key objects.
using var doc = JsonDocument.Parse (jsonString);
if (doc.RootElement.GetProperty ("Themes").ValueKind != JsonValueKind.Array)
    throw new FormatException ("Themes must be a JSON array [ {key: value}, ... ].");

Type guard

static bool ThemesIsArrayShape (JsonElement themes) =>
    themes.ValueKind == JsonValueKind.Array
    && themes.EnumerateArray ().All (e => e.ValueKind == JsonValueKind.Object && e.EnumerateObject ().Count () == 1);

Try / catch

try { ConfigurationManager.Load (locations); }
catch (JsonException ex) when (ex.Message.Contains ("Expected a JSON array"))
{ /* rewrap the object-form Themes into an array */ }

Prevention

When it happens

Trigger: A config file provides Themes as a JSON object { "Default": {...} } instead of an array [ {"Default": {...}} ]. Also fires on a bare scalar or string where the array is expected.

Common situations: Author assumes the natural JSON object shape for a map; migrating from a system that uses object-form maps; hand-editing a Themes section.

Related errors


AI-assisted analysis of tui-cs/Terminal.Gui@2e47b11478 (2026-08-13). Data as JSON: /api/errors/7f4133fc13e6f832. Report an issue: GitHub.