| 1 | // Licensed to the .NET Foundation under one or more agreements. |
| 2 | // The .NET Foundation licenses this file to you under the MIT license. |
| 3 | |
| 4 | using System.ComponentModel; |
| 5 | using System.Diagnostics.CodeAnalysis; |
| 6 | #if !NET9_0_OR_GREATER |
| 7 | using System.Reflection; |
| 8 | #endif |
| 9 | using System.Text.Json; |
| 10 | using System.Text.Json.Serialization; |
| 11 | #if !NET9_0_OR_GREATER |
| 12 | using AgentClientProtocol; |
| 13 | #endif |
| 14 | |
| 15 | // NOTE: |
| 16 | // This is a workaround for lack of System.Text.Json's JsonStringEnumConverter<T> |
| 17 | // 9.x support for JsonStringEnumMemberNameAttribute. Once all builds use the System.Text.Json 9.x |
| 18 | // version, this whole file can be removed. Note that the type is public so that external source |
| 19 | // generators can use it, so removing it is a potential breaking change. |
| 20 | |
| 21 | namespace AgentClientProtocol |
| 22 | { |
| 23 | /// <summary> |
| 24 | /// A JSON converter for enums that allows customizing the serialized string value of enum members |
| 25 | /// using the <see cref="JsonStringEnumMemberNameAttribute"/>. |
| 26 | /// </summary> |
| 27 | /// <typeparam name="TEnum">The enum type to convert.</typeparam> |
| 28 | /// <remarks> |
| 29 | /// This is a temporary workaround for lack of System.Text.Json's JsonStringEnumConverter<T> |
| 30 | /// 9.x support for custom enum member naming. It will be replaced by the built-in functionality |
| 31 | /// once .NET 9 is fully adopted. |
| 32 | /// </remarks> |
| 33 | [EditorBrowsable(EditorBrowsableState.Never)] |
| 34 | public sealed class CustomizableJsonStringEnumConverter< |
| 35 | #if NET8_0_OR_GREATER |
| 36 | [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicFields)] |
| 37 | #endif |
| 38 | TEnum> : |
| 39 | JsonStringEnumConverter<TEnum> where TEnum : struct, Enum |
| 40 | { |
| 41 | #if !NET9_0_OR_GREATER |
| 42 | /// <summary> |
| 43 | /// Initializes a new instance of the <see cref="CustomizableJsonStringEnumConverter{TEnum}"/> class. |
| 44 | /// </summary> |
| 45 | /// <remarks> |
| 46 | /// The converter automatically detects any enum members decorated with <see cref="JsonStringEnumMemberNameAttribute"/> |
| 47 | /// and uses those values during serialization and deserialization. |
| 48 | /// </remarks> |
| 49 | public CustomizableJsonStringEnumConverter() : |
| 50 | base(namingPolicy: ResolveNamingPolicy()) |
| 51 | { |
| 52 | } |
| 53 | |
| 54 | private static JsonNamingPolicy? ResolveNamingPolicy() |
| 55 | { |
| 56 | var map = typeof(TEnum).GetFields(BindingFlags.Public | BindingFlags.Static) |
| 57 | .Select(f => (f.Name, AttributeName: f.GetCustomAttribute<JsonStringEnumMemberNameAttribute>()?.Name)) |
| 58 | .Where(pair => pair.AttributeName != null) |
| 59 | .ToDictionary(pair => pair.Name, pair => pair.AttributeName); |
| 60 | |
| 61 | return map.Count > 0 ? new EnumMemberNamingPolicy(map!) : null; |
| 62 | } |
| 63 | |
| 64 | private sealed class EnumMemberNamingPolicy(Dictionary<string, string> map) : JsonNamingPolicy |
| 65 | { |
| 66 | public override string ConvertName(string name) => |
| 67 | map.TryGetValue(name, out string? newName) ? |
| 68 | newName : |
| 69 | name; |
| 70 | } |
| 71 | #endif |
| 72 | } |
| 73 | |
| 74 | /// <summary> |
| 75 | /// A JSON converter for enums that allows customizing the serialized string value of enum members |
| 76 | /// using the <see cref="JsonStringEnumMemberNameAttribute"/>. |
| 77 | /// </summary> |
| 78 | /// <remarks> |
| 79 | /// This is a temporary workaround for lack of System.Text.Json's JsonStringEnumConverter<T> |
| 80 | /// 9.x support for custom enum member naming. It will be replaced by the built-in functionality |
| 81 | /// once .NET 9 is fully adopted. |
| 82 | /// </remarks> |
| 83 | [EditorBrowsable(EditorBrowsableState.Never)] |
| 84 | #if NET8_0_OR_GREATER |
| 85 | [RequiresUnreferencedCode("Requires unreferenced code to instantiate the generic enum converter.")] |
| 86 | [RequiresDynamicCode("Requires dynamic code to instantiate the generic enum converter.")] |
| 87 | #endif |
| 88 | public sealed class CustomizableJsonStringEnumConverter : JsonConverterFactory |
| 89 | { |
| 90 | /// <inheritdoc/> |
| 91 | public override bool CanConvert(Type typeToConvert) => typeToConvert.IsEnum; |
| 92 | /// <inheritdoc/> |
| 93 | public override JsonConverter? CreateConverter(Type typeToConvert, JsonSerializerOptions options) |
| 94 | { |
| 95 | Type converterType = typeof(CustomizableJsonStringEnumConverter<>).MakeGenericType(typeToConvert)!; |
| 96 | var factory = (JsonConverterFactory)Activator.CreateInstance(converterType)!; |
| 97 | return factory.CreateConverter(typeToConvert, options); |
| 98 | } |
| 99 | } |
| 100 | } |
| 101 | |
| 102 | #if !NET9_0_OR_GREATER |
| 103 | namespace System.Text.Json.Serialization |
| 104 | { |
| 105 | /// <summary> |
| 106 | /// Determines the custom string value that should be used when serializing an enum member using JSON. |
| 107 | /// </summary> |
| 108 | /// <remarks> |
| 109 | /// This attribute is a temporary workaround for lack of System.Text.Json's support for custom enum member naming |
| 110 | /// in versions prior to .NET 9. It works together with <see cref="CustomizableJsonStringEnumConverter{TEnum}"/> |
| 111 | /// to provide customized string representations of enum values during JSON serialization and deserialization. |
| 112 | /// </remarks> |
| 113 | [AttributeUsage(AttributeTargets.Field, AllowMultiple = false)] |
| 114 | internal sealed class JsonStringEnumMemberNameAttribute : Attribute |
| 115 | { |
| 116 | /// <summary> |
| 117 | /// Creates new attribute instance with a specified enum member name. |
| 118 | /// </summary> |
| 119 | /// <param name="name">The name to apply to the current enum member when serialized to JSON.</param> |
| 120 | public JsonStringEnumMemberNameAttribute(string name) |
| 121 | { |
| 122 | Name = name; |
| 123 | } |
| 124 | |
| 125 | /// <summary> |
| 126 | /// Gets the custom JSON name of the enum member. |
| 127 | /// </summary> |
| 128 | public string Name { get; } |
| 129 | } |
| 130 | } |
| 131 | #endif |