{"record":{"id":"fb4be352a1dce527","repo":"tui-cs/Terminal.Gui","slug":"p-key-unsupported-configuration-converter-type","errorCode":null,"errorMessage":"{p.Key}: Unsupported configuration converter type \"{converterType.FullName}\" when dynamic code is unavailable.","messagePattern":"(.+?): Unsupported configuration converter type \"(.+?)\" when dynamic code is unavailable\\.","errorType":"exception","errorClass":"JsonException","httpStatus":null,"severity":"error","filePath":"Terminal.Gui/Configuration/ScopeJsonConverter.cs","lineNumber":203,"sourceCode":"            {\n                if (propertyValue is null)\n                {\n                    writer.WriteNullValue ();\n\n                    continue;\n                }\n\n                if (TryWriteWithKnownConverter (writer, propertyType, converterType, propertyValue, options))\n                {\n                    continue;\n                }\n\n                if (TryWriteWithDynamicConverter (writer, propertyType, converterType, propertyValue, options))\n                {\n                    continue;\n                }\n\n                throw new JsonException ($\"{p.Key}: Unsupported configuration converter type \\\"{converterType.FullName}\\\" when dynamic code is unavailable.\");\n            }\n            object? prop = propertyValue;\n\n            if (prop == null)\n            {\n                writer.WriteNullValue ();\n            }\n            else\n            {\n                if (TryWriteEnumValue (writer, prop.GetType (), prop))\n                {\n                    continue;\n                }\n\n                JsonTypeInfo? jsonTypeInfo = TuiSerializerContext.Instance.GetTypeInfo (prop.GetType ());\n\n                if (jsonTypeInfo is null)\n                {","sourceCodeStart":185,"sourceCodeEnd":221,"githubUrl":"https://github.com/tui-cs/Terminal.Gui/blob/2e47b11478db083499917f2ad27c34d30efb0df1/Terminal.Gui/Configuration/ScopeJsonConverter.cs#L185-L221","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","solutions":["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).","If you control a fork, add the converter type to the TryWriteWithKnownConverter allow-list in ScopeJsonConverter.cs so it is invoked without reflection.","Disable NativeAOT/trimming for the app, or mark the assembly as trim-unfriendly, so RuntimeFeature.IsDynamicCodeSupported stays true and the dynamic path runs.","Avoid calling the serialization path at all (don't write config out) under AOT; only deserialize using registered types."],"exampleFix":"// before\n[JsonConverter (typeof (MyCustomSchemeConverter))]\n[ConfigurationProperty (Scope = typeof (ThemeScope))]\npublic static Scheme? MyProp { get; set; }\n\n// after - let the source generator handle it\n[ConfigurationProperty (Scope = typeof (ThemeScope))]\npublic static Scheme? MyProp { get; set; }\n// and register Scheme in SourceGenerationContext via [JsonSerializable(typeof(Scheme))]","handlingStrategy":"validation","validationCode":"// Before publishing AOT, assert no config property uses an unregistered converter\nif (!RuntimeFeature.IsDynamicCodeSupported)\n{\n    // Avoid writing config out, or ensure all ConverterTypes are in the known list:\n    //   ConcurrentDictionaryJsonConverter<ThemeScope>, DictionaryJsonConverter<Scheme?>, TraceCategoryJsonConverter\n}","typeGuard":null,"tryCatchPattern":"try { string json = sourcesManager.ToJson (scope); }\ncatch (JsonException ex) when (ex.Message.Contains (\"Unsupported configuration converter type\"))\n{ /* fall back to not serializing, or log and skip AOT publish */ }","preventionTips":["Do not decorate ConfigurationProperty types with custom JsonConverters; register types with the source generator instead.","Run a trimmed/AOT test build in CI that round-trips config to catch unregistered converters.","Maintain the TryWriteWithKnownConverter allow-list if you fork Terminal.Gui."],"tags":["config","json","serialization","aot","trimming","nativeaot"],"backgroundTag":null,"analyzedSha":"2e47b11478db083499917f2ad27c34d30efb0df1","analyzedAt":"2026-08-13T19:20:08.826Z","schemaVersion":2},"datasetVersion":"2026-08-14T00:17:13.853Z"}