tui-cs/Terminal.Gui · error · JsonException

{propertyName}: Json error in ScopeJsonConverter

Error message

{propertyName}: Json error in ScopeJsonConverter

What it means

Terminal-final fallback thrown at the end of ScopeJsonConverter.Read when the Utf8JsonReader loop exits without returning a parsed scope object. In practice this means the JSON document for a configuration scope (SettingsScope/ThemeScope/AppSettingsScope) was truncated or malformed so the reader never produced the matching EndObject token before the stream ended. The message is intentionally generic because the parser could not associate the failure with a specific property.

Source

Thrown at Terminal.Gui/Configuration/ScopeJsonConverter.cs:146

                                                                 })
                                                         .FirstOrDefault ();

                if (property is { })
                {
                    // Set the value of propertyName on the scopeT.
                    PropertyInfo prop = typeof (TScopeT).GetProperty (propertyName!)!;

                    prop.SetValue (scope, JsonSerializer.Deserialize (ref reader, prop.PropertyType, TuiSerializerContext.Instance));
                }
                else
                {
                    // Unknown property
                    throw new JsonException ($"{propertyName}: Unknown property name.");
                }
            }
        }

        throw new JsonException ($"{propertyName}: Json error in ScopeJsonConverter");
    }

    [UnconditionalSuppressMessage ("AOT",
                                   "IL3050:Calling members annotated with 'RequiresDynamicCodeAttribute' may break functionality when AOT compiling.",
                                   Justification =
                                       "Arbitrary property-level converter fallback is guarded by RuntimeFeature.IsDynamicCodeSupported and is unreachable under NativeAOT.")]
    [UnconditionalSuppressMessage ("Trimming",
                                   "IL2026:Members annotated with 'RequiresUnreferencedCodeAttribute' require dynamic access otherwise can break functionality when trimming application code",
                                   Justification =
                                       "Arbitrary property-level converter fallback is only used when a consumer opts into a custom property-level JsonConverter on JIT-supported runtimes.")]
    public override void Write (Utf8JsonWriter writer, TScopeT scope, JsonSerializerOptions options)
    {
        writer.WriteStartObject ();

        IEnumerable<PropertyInfo> properties = typeof (TScopeT).GetProperties ().Where (p => p.GetCustomAttribute (typeof (JsonIncludeAttribute)) != null);

        foreach (PropertyInfo p in properties)
        {

View on GitHub (pinned to 2e47b11478)

Solutions

  1. Validate the offending config file with a JSON linter (e.g. 'dotnet run --project ...' won't help; use 'jq . config.json' or a VS Code JSON validator) and add the missing closing brace.
  2. Identify which source is broken: check ConfigurationManager.Sources (the ConcurrentDictionary<ConfigLocations,string>) for the file paths/resources that were probed, then validate each one.
  3. Enable ConfigurationManager.ThrowOnJsonErrors = true early in startup so the exact source and message surface as a real exception with a path, instead of being swallowed and printed at shutdown.
  4. If the stream comes from your own code, ensure you flush and reset Position before passing it to SourcesManager.Load(Stream,...).

Example fix

// before: config.json on disk
{
  "Theme": "Default"
// after
{
  "Theme": "Default"
}
Defensive patterns

Strategy: validation

Validate before calling

// Validate a config file is well-formed JSON with an object root before Terminal.Gui loads it
bool IsValidConfigRoot (string path)
{
    try
    {
        using JsonDocument doc = JsonDocument.Parse (File.ReadAllText (path));
        return doc.RootElement.ValueKind == JsonValueKind.Object;
    }
    catch { return false; }
}

Try / catch

// Keep ThrowOnJsonErrors false (default) so a broken file is logged, not fatal; or wrap explicitly
ConfigurationManager.ThrowOnJsonErrors = false; // default
try { /* trigger config load */ }
catch (JsonException ex) { /* only if you set ThrowOnJsonErrors=true */ Logging.Error (ex.Message); }

Prevention

When it happens

Trigger: Deserializing a config JSON stream (via SourcesManager.Load -> JsonSerializer.Deserialize with TuiSerializerContext.Instance.SettingsScope) where the byte stream ends mid-object — e.g. a config.json file cut off after "\"Theme\": \"Default\"" with no closing brace, or a network/resource stream that returned EOF early. Also reachable if reader.Read() returns false because the stream is empty after the StartObject token.

Common situations: A user hand-edits .tui/config.json or <App>.config.json and forgets the closing brace; an editor or sync tool truncated the file; an embedded resource was generated incorrectly; the TUI_CONFIG environment variable points at a partial file.

Related errors


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