tui-cs/Terminal.Gui · error · JsonException

: Unsupported configuration converter type " " when dynamic…

Error message

{p.Key}: Unsupported configuration converter type "{converterType.FullName}" when dynamic code is unavailable.

What it means

Thrown in ScopeJsonConverter.Write when a configuration property carries a ConverterType (a property-level JsonConverter declared via the ConfigProperty) that is not one of the three hard-coded known converters (ConcurrentDictionaryJsonConverter<ThemeScope>, DictionaryJsonConverter<Scheme?>, TraceCategoryJsonConverter) AND the runtime does not support dynamic code (RuntimeFeature.IsDynamicCodeSupported is false). Under NativeAOT or a trimmed app, the reflective TryWriteWithDynamicConverter path is disabled, so an unregistered custom converter cannot be invoked and serialization aborts.

Solutions

  1. Remove the custom JsonConverter from the configuration property and rely on the source-generated TuiSerializerContext (register the property's type via [JsonSerializable] on SourceGenerationContext instead).
  2. If you control a fork, add the converter type to the TryWriteWithKnownConverter allow-list in ScopeJsonConverter.cs so it is invoked without reflection.
  3. Disable NativeAOT/trimming for the app, or mark the assembly as trim-unfriendly, so RuntimeFeature.IsDynamicCodeSupported stays true and the dynamic path runs.
  4. Avoid calling the serialization path at all (don't write config out) under AOT; only deserialize using registered types.

Example fix

// before
[JsonConverter (typeof (MyCustomSchemeConverter))]
[ConfigurationProperty (Scope = typeof (ThemeScope))]
public static Scheme? MyProp { get; set; }

// after - let the source generator handle it
[ConfigurationProperty (Scope = typeof (ThemeScope))]
public static Scheme? MyProp { get; set; }
// and register Scheme in SourceGenerationContext via [JsonSerializable(typeof(Scheme))]
Defensive patterns

Strategy: validation

Validate before calling

// Before publishing AOT, assert no config property uses an unregistered converter
if (!RuntimeFeature.IsDynamicCodeSupported)
{
    // Avoid writing config out, or ensure all ConverterTypes are in the known list:
    //   ConcurrentDictionaryJsonConverter<ThemeScope>, DictionaryJsonConverter<Scheme?>, TraceCategoryJsonConverter
}

Try / catch

try { string json = sourcesManager.ToJson (scope); }
catch (JsonException ex) when (ex.Message.Contains ("Unsupported configuration converter type"))
{ /* fall back to not serializing, or log and skip AOT publish */ }

Prevention

When it happens

Trigger: Publishing with dotnet publish -r <rid> /p:PublishAot=true (or with <PublishTrimmed>true</PublishTrimmed>) while a configuration property is annotated with a JsonConverter that is not in the known-converter allow-list. ConfigurationManager.Serialize/ToJson (or writing a config back out) then hits this arm because TryWriteWithKnownConverter and TryWriteWithDynamicConverter both return false.

Common situations: A library consumer adds a custom config property with [JsonConverter(typeof(MyConverter))] and ships an AOT build; upgrading Terminal.Gui in a previously-working AOT app if a new property introduces a new converter type not yet in the known list.

Related errors


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

Appendix: source

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

            {
                if (propertyValue is null)
                {
                    writer.WriteNullValue ();

                    continue;
                }

                if (TryWriteWithKnownConverter (writer, propertyType, converterType, propertyValue, options))
                {
                    continue;
                }

                if (TryWriteWithDynamicConverter (writer, propertyType, converterType, propertyValue, options))
                {
                    continue;
                }

                throw new JsonException ($"{p.Key}: Unsupported configuration converter type \"{converterType.FullName}\" when dynamic code is unavailable.");
            }
            object? prop = propertyValue;

            if (prop == null)
            {
                writer.WriteNullValue ();
            }
            else
            {
                if (TryWriteEnumValue (writer, prop.GetType (), prop))
                {
                    continue;
                }

                JsonTypeInfo? jsonTypeInfo = TuiSerializerContext.Instance.GetTypeInfo (prop.GetType ());

                if (jsonTypeInfo is null)
                {

View on GitHub (pinned to 2e47b11478)