Make enums serialize as strings if using the reflection-based serializer. (#473)

* Make enums serialize as strings if using the reflection-based serializer.

* Remove unnecessary partial

* Use an explicit reflection-based converter for enums.

* Update comments

* Make the reflection-based enum converter public.
This commit is contained in:
Eirik Tsarpalis 2025-06-12 17:25:54 +02:00 • committed by GitHub
commit 1e8fb04037
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 61 additions and 2 deletions

View file

@ -5,8 +5,8 @@ using System.ComponentModel;
using System.Diagnostics.CodeAnalysis;
#if !NET9_0_OR_GREATER
using System.Reflection;
using System.Text.Json;
#endif
using System.Text.Json;
using System.Text.Json.Serialization;
#if !NET9_0_OR_GREATER
using ModelContextProtocol;
@ -66,6 +66,31 @@ namespace ModelContextProtocol
}
#endif
}
/// <summary>
/// A JSON converter for enums that allows customizing the serialized string value of enum members
/// using the <see cref="JsonStringEnumMemberNameAttribute"/>.
/// </summary>
/// <remarks>
/// This is a temporary workaround for lack of System.Text.Json's JsonStringEnumConverter&lt;T&gt;
/// 9.x support for custom enum member naming. It will be replaced by the built-in functionality
/// once .NET 9 is fully adopted.
/// </remarks>
[EditorBrowsable(EditorBrowsableState.Never)]
[RequiresUnreferencedCode("Requires unreferenced code to instantiate the generic enum converter.")]
[RequiresDynamicCode("Requires dynamic code to instantiate the generic enum converter.")]
public sealed class CustomizableJsonStringEnumConverter : JsonConverterFactory
{
/// <inheritdoc/>
public override bool CanConvert(Type typeToConvert) => typeToConvert.IsEnum;
/// <inheritdoc/>
public override JsonConverter? CreateConverter(Type typeToConvert, JsonSerializerOptions options)
{
Type converterType = typeof(CustomizableJsonStringEnumConverter<>).MakeGenericType(typeToConvert)!;
var factory = (JsonConverterFactory)Activator.CreateInstance(converterType)!;
return factory.CreateConverter(typeToConvert, options);
}
}
}
#if !NET9_0_OR_GREATER

View file

@ -2,6 +2,7 @@
using ModelContextProtocol.Protocol;
using ModelContextProtocol.Server;
using System.Diagnostics.CodeAnalysis;
using System.Reflection;
using System.Text.Json;
using System.Text.Json.Serialization;
using System.Text.Json.Serialization.Metadata;
@ -34,14 +35,22 @@ public static partial class McpJsonUtilities
/// Creates default options to use for MCP-related serialization.
/// </summary>
/// <returns>The configured options.</returns>
[UnconditionalSuppressMessage("ReflectionAnalysis", "IL3050:RequiresDynamicCode", Justification = "Converter is guarded by IsReflectionEnabledByDefault check.")]
[UnconditionalSuppressMessage("Trimming", "IL2026:Members annotated with 'RequiresUnreferencedCodeAttribute' require dynamic access", Justification = "Converter is guarded by IsReflectionEnabledByDefault check.")]
private static JsonSerializerOptions CreateDefaultOptions()
{
// Copy the configuration from the source generated context.
JsonSerializerOptions options = new(JsonContext.Default.Options);
// Chain with all supported types from MEAI
// Chain with all supported types from MEAI.
options.TypeInfoResolverChain.Add(AIJsonUtilities.DefaultOptions.TypeInfoResolver!);
// Add a converter for user-defined enums, if reflection is enabled by default.
if (JsonSerializer.IsReflectionEnabledByDefault)
{
options.Converters.Add(new CustomizableJsonStringEnumConverter());
}
options.MakeReadOnly();
return options;
}