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:
parent
d7d9eab3f8
commit
1e8fb04037
3 changed files with 61 additions and 2 deletions
|
|
@ -5,8 +5,8 @@ using System.ComponentModel;
|
||||||
using System.Diagnostics.CodeAnalysis;
|
using System.Diagnostics.CodeAnalysis;
|
||||||
#if !NET9_0_OR_GREATER
|
#if !NET9_0_OR_GREATER
|
||||||
using System.Reflection;
|
using System.Reflection;
|
||||||
using System.Text.Json;
|
|
||||||
#endif
|
#endif
|
||||||
|
using System.Text.Json;
|
||||||
using System.Text.Json.Serialization;
|
using System.Text.Json.Serialization;
|
||||||
#if !NET9_0_OR_GREATER
|
#if !NET9_0_OR_GREATER
|
||||||
using ModelContextProtocol;
|
using ModelContextProtocol;
|
||||||
|
|
@ -66,6 +66,31 @@ namespace ModelContextProtocol
|
||||||
}
|
}
|
||||||
#endif
|
#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<T>
|
||||||
|
/// 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
|
#if !NET9_0_OR_GREATER
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,7 @@
|
||||||
using ModelContextProtocol.Protocol;
|
using ModelContextProtocol.Protocol;
|
||||||
using ModelContextProtocol.Server;
|
using ModelContextProtocol.Server;
|
||||||
using System.Diagnostics.CodeAnalysis;
|
using System.Diagnostics.CodeAnalysis;
|
||||||
|
using System.Reflection;
|
||||||
using System.Text.Json;
|
using System.Text.Json;
|
||||||
using System.Text.Json.Serialization;
|
using System.Text.Json.Serialization;
|
||||||
using System.Text.Json.Serialization.Metadata;
|
using System.Text.Json.Serialization.Metadata;
|
||||||
|
|
@ -34,14 +35,22 @@ public static partial class McpJsonUtilities
|
||||||
/// Creates default options to use for MCP-related serialization.
|
/// Creates default options to use for MCP-related serialization.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <returns>The configured options.</returns>
|
/// <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()
|
private static JsonSerializerOptions CreateDefaultOptions()
|
||||||
{
|
{
|
||||||
// Copy the configuration from the source generated context.
|
// Copy the configuration from the source generated context.
|
||||||
JsonSerializerOptions options = new(JsonContext.Default.Options);
|
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!);
|
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();
|
options.MakeReadOnly();
|
||||||
return options;
|
return options;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,6 @@
|
||||||
using System.Text.Json;
|
using System.Text.Json;
|
||||||
|
using System.Text.Json.Serialization;
|
||||||
|
using System.Text.Json.Serialization.Metadata;
|
||||||
|
|
||||||
namespace ModelContextProtocol.Tests;
|
namespace ModelContextProtocol.Tests;
|
||||||
|
|
||||||
|
|
@ -22,4 +24,27 @@ public static class McpJsonUtilitiesTests
|
||||||
|
|
||||||
Assert.Equal(JsonSerializer.IsReflectionEnabledByDefault, options.TryGetTypeInfo(anonType, out _));
|
Assert.Equal(JsonSerializer.IsReflectionEnabledByDefault, options.TryGetTypeInfo(anonType, out _));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public static void DefaultOptions_UnknownEnumHandling()
|
||||||
|
{
|
||||||
|
var options = McpJsonUtilities.DefaultOptions;
|
||||||
|
|
||||||
|
if (JsonSerializer.IsReflectionEnabledByDefault)
|
||||||
|
{
|
||||||
|
Assert.Equal("\"A\"", JsonSerializer.Serialize(EnumWithoutAnnotation.A, options));
|
||||||
|
Assert.Equal("\"A\"", JsonSerializer.Serialize(EnumWithAnnotation.A, options));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
options = new(options) { TypeInfoResolver = new DefaultJsonTypeInfoResolver() };
|
||||||
|
Assert.Equal("1", JsonSerializer.Serialize(EnumWithoutAnnotation.A, options));
|
||||||
|
Assert.Equal("\"A\"", JsonSerializer.Serialize(EnumWithAnnotation.A, options));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public enum EnumWithoutAnnotation { A = 1, B = 2, C = 3 }
|
||||||
|
|
||||||
|
[JsonConverter(typeof(JsonStringEnumConverter<EnumWithAnnotation>))]
|
||||||
|
public enum EnumWithAnnotation { A = 1, B = 2, C = 3 }
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue