<PackageReference Include="System.Text.Json" Version="9.0.5" />

EnumConverter<T>

sealed class EnumConverter<T> : JsonPrimitiveConverter<T> where T : struct, Enum
using System.Buffers; using System.Collections.Concurrent; using System.Collections.Generic; using System.Reflection; using System.Runtime.CompilerServices; using System.Text.Encodings.Web; using System.Text.Json.Nodes; using System.Text.Json.Schema; namespace System.Text.Json.Serialization.Converters { internal sealed class EnumConverter<T> : JsonPrimitiveConverter<T> where T : struct, Enum { private sealed class EnumFieldInfo { private List<EnumFieldInfo> _conflictingFields; public EnumFieldNameKind Kind { get; } = kind; public ulong Key { get; } = key; public string OriginalName { get; } = originalName; public string JsonName { get; } = jsonName; public EnumFieldInfo(ulong key, EnumFieldNameKind kind, string originalName, string jsonName) { } public void AppendConflictingField(EnumFieldInfo other) { if (!<AppendConflictingField>g__ConflictsWith|14_0(this, other)) { List<EnumFieldInfo> list = _conflictingFields ?? (_conflictingFields = new List<EnumFieldInfo>()); foreach (EnumFieldInfo item in list) { if (<AppendConflictingField>g__ConflictsWith|14_0(item, other)) return; } list.Add(other); } } public EnumFieldInfo GetMatchingField(ReadOnlySpan<char> input) { if (Kind == EnumFieldNameKind.Default || MemoryExtensions.SequenceEqual<char>(input, JsonName.AsSpan())) return this; List<EnumFieldInfo> conflictingFields = _conflictingFields; if (conflictingFields != null) { foreach (EnumFieldInfo item in conflictingFields) { if (item.Kind == EnumFieldNameKind.Default || MemoryExtensions.SequenceEqual<char>(input, item.JsonName.AsSpan())) return item; } } return null; } } private enum EnumFieldNameKind { Default, NamingPolicy, Attribute } private static readonly TypeCode s_enumTypeCode = Type.GetTypeCode(typeof(T)); private static readonly bool s_isSignedEnum = (int)s_enumTypeCode % 2 == 1; private static readonly bool s_isFlagsEnum = typeof(T).IsDefined(typeof(FlagsAttribute), false); private readonly EnumConverterOptions _converterOptions; private readonly JsonNamingPolicy _namingPolicy; private readonly EnumFieldInfo[] _enumFieldInfo; private readonly Dictionary<string, EnumFieldInfo> _enumFieldInfoIndex; private readonly ConcurrentDictionary<ulong, JsonEncodedText> _nameCacheForWriting; private readonly ConcurrentDictionary<string, ulong> _nameCacheForReading; private const int NameCacheSizeSoftLimit = 64; public EnumConverter(EnumConverterOptions converterOptions, JsonNamingPolicy namingPolicy, JsonSerializerOptions options) { _converterOptions = converterOptions; _namingPolicy = namingPolicy; _enumFieldInfo = ResolveEnumFields(namingPolicy); _enumFieldInfoIndex = new Dictionary<string, EnumFieldInfo>(StringComparer.OrdinalIgnoreCase); _nameCacheForWriting = new ConcurrentDictionary<ulong, JsonEncodedText>(); _nameCacheForReading = new ConcurrentDictionary<string, ulong>(StringComparer.Ordinal); JavaScriptEncoder encoder = options.Encoder; EnumFieldInfo[] enumFieldInfo = _enumFieldInfo; foreach (EnumFieldInfo enumFieldInfo2 in enumFieldInfo) { <.ctor>g__AddToEnumFieldIndex|10_0(enumFieldInfo2); JsonEncodedText value = JsonEncodedText.Encode(enumFieldInfo2.JsonName, encoder); _nameCacheForWriting.TryAdd(enumFieldInfo2.Key, value); _nameCacheForReading.TryAdd(enumFieldInfo2.JsonName, enumFieldInfo2.Key); } if (namingPolicy != null) { enumFieldInfo = _enumFieldInfo; foreach (EnumFieldInfo enumFieldInfo3 in enumFieldInfo) { if (enumFieldInfo3.Kind == EnumFieldNameKind.NamingPolicy) <.ctor>g__AddToEnumFieldIndex|10_0(new EnumFieldInfo(enumFieldInfo3.Key, EnumFieldNameKind.Default, enumFieldInfo3.OriginalName, enumFieldInfo3.OriginalName)); } } } public override T Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) { switch (reader.TokenType) { case JsonTokenType.String: if ((_converterOptions & EnumConverterOptions.AllowStrings) != 0 && TryParseEnumFromString(ref reader, out T result)) return result; break; case JsonTokenType.Number: if ((_converterOptions & EnumConverterOptions.AllowNumbers) != 0) { switch (s_enumTypeCode) { case TypeCode.Int32: if (reader.TryGetInt32(out int value8)) return Unsafe.As<int, T>(ref value8); break; case TypeCode.UInt32: if (reader.TryGetUInt32(out uint value4)) return Unsafe.As<uint, T>(ref value4); break; case TypeCode.Int64: if (reader.TryGetInt64(out long value6)) return Unsafe.As<long, T>(ref value6); break; case TypeCode.UInt64: if (reader.TryGetUInt64(out ulong value2)) return Unsafe.As<ulong, T>(ref value2); break; case TypeCode.Byte: if (reader.TryGetByte(out byte value7)) return Unsafe.As<byte, T>(ref value7); break; case TypeCode.SByte: if (reader.TryGetSByte(out sbyte value5)) return Unsafe.As<sbyte, T>(ref value5); break; case TypeCode.Int16: if (reader.TryGetInt16(out short value3)) return Unsafe.As<short, T>(ref value3); break; case TypeCode.UInt16: if (reader.TryGetUInt16(out ushort value)) return Unsafe.As<ushort, T>(ref value); break; } } break; } ThrowHelper.ThrowJsonException(null); return default(T); } public override void Write(Utf8JsonWriter writer, T value, JsonSerializerOptions options) { EnumConverterOptions converterOptions = _converterOptions; if ((converterOptions & EnumConverterOptions.AllowStrings) != 0) { ulong key = ConvertToUInt64(value); if (_nameCacheForWriting.TryGetValue(key, out JsonEncodedText value2)) { writer.WriteStringValue(value2); return; } if (IsDefinedValueOrCombinationOfValues(key)) { string value3 = FormatEnumAsString(key, value, null); if (_nameCacheForWriting.Count < 64) { value2 = JsonEncodedText.Encode(value3, options.Encoder); writer.WriteStringValue(value2); _nameCacheForWriting.TryAdd(key, value2); } else writer.WriteStringValue(value3); return; } } if ((converterOptions & EnumConverterOptions.AllowNumbers) == (EnumConverterOptions)0) ThrowHelper.ThrowJsonException(null); if (s_isSignedEnum) writer.WriteNumberValue(ConvertToInt64(value)); else writer.WriteNumberValue(ConvertToUInt64(value)); } internal override T ReadAsPropertyNameCore(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) { if (!TryParseEnumFromString(ref reader, out T result)) ThrowHelper.ThrowJsonException(null); return result; } internal override void WriteAsPropertyNameCore(Utf8JsonWriter writer, T value, JsonSerializerOptions options, bool isWritingExtensionDataProperty) { JsonNamingPolicy dictionaryKeyPolicy = options.DictionaryKeyPolicy; JsonNamingPolicy jsonNamingPolicy = (dictionaryKeyPolicy != null && dictionaryKeyPolicy != _namingPolicy) ? dictionaryKeyPolicy : null; ulong num = ConvertToUInt64(value); if (jsonNamingPolicy == null && _nameCacheForWriting.TryGetValue(num, out JsonEncodedText value2)) writer.WritePropertyName(value2); else if (IsDefinedValueOrCombinationOfValues(num)) { string text = FormatEnumAsString(num, value, jsonNamingPolicy); if (jsonNamingPolicy == null && _nameCacheForWriting.Count < 64) { value2 = JsonEncodedText.Encode(text, options.Encoder); writer.WritePropertyName(value2); _nameCacheForWriting.TryAdd(num, value2); } else writer.WritePropertyName(text); } else if (s_isSignedEnum) { writer.WritePropertyName(ConvertToInt64(value)); } else { writer.WritePropertyName(num); } } private unsafe bool TryParseEnumFromString(ref Utf8JsonReader reader, out T result) { int valueLength = reader.ValueLength; char[] array = null; Span<char> span = (valueLength > 128) ? ((Span<char>)(array = ArrayPool<char>.Shared.Rent(valueLength))) : new Span<char>(stackalloc byte[256], 128); Span<char> span2 = span; int length = reader.CopyString(span2); span2 = span2.Slice(0, length); string text = ((ReadOnlySpan<char>)span2).Trim().ToString(); ConcurrentDictionary<string, ulong> nameCacheForReading = _nameCacheForReading; bool flag; if (nameCacheForReading.TryGetValue(text, out ulong value)) { result = ConvertFromUInt64(value); flag = true; } else { if (JsonHelpers.IntegerRegex.IsMatch(text)) { if ((_converterOptions & EnumConverterOptions.AllowNumbers) != 0) flag = Enum.TryParse<T>(text, out result); else { result = default(T); flag = false; } } else flag = TryParseNamedEnum(text, out result); if (flag && _nameCacheForReading.Count < 64) nameCacheForReading.TryAdd(text, ConvertToUInt64(result)); } if (array != null) { span2.Clear(); ArrayPool<char>.Shared.Return(array, false); } return flag; } private bool TryParseNamedEnum(string source, out T result) { Dictionary<string, EnumFieldInfo> enumFieldInfoIndex = _enumFieldInfoIndex; ReadOnlySpan<char> readOnlySpan = source.AsSpan(); ulong num = 0; while (true) { int num2 = MemoryExtensions.IndexOf<char>(readOnlySpan, ','); ReadOnlySpan<char> input; if (num2 == -1) { input = readOnlySpan; readOnlySpan = default(ReadOnlySpan<char>); } else { input = readOnlySpan.Slice(0, num2).TrimEnd(); readOnlySpan = readOnlySpan.Slice(num2 + 1).TrimStart(); } if (enumFieldInfoIndex.TryGetValue(input.ToString(), out EnumFieldInfo value)) { EnumFieldInfo matchingField = value.GetMatchingField(input); if (matchingField != null) { num |= matchingField.Key; if (readOnlySpan.IsEmpty) break; continue; } } result = default(T); return false; } result = ConvertFromUInt64(num); return true; } private static ulong ConvertToUInt64(T value) { switch (s_enumTypeCode) { case TypeCode.Int32: case TypeCode.UInt32: return Unsafe.As<T, uint>(ref value); case TypeCode.Int64: case TypeCode.UInt64: return Unsafe.As<T, ulong>(ref value); case TypeCode.Int16: case TypeCode.UInt16: return Unsafe.As<T, ushort>(ref value); default: return Unsafe.As<T, byte>(ref value); } } private static long ConvertToInt64(T value) { switch (s_enumTypeCode) { case TypeCode.Int32: return Unsafe.As<T, int>(ref value); case TypeCode.Int64: return Unsafe.As<T, long>(ref value); case TypeCode.Int16: return Unsafe.As<T, short>(ref value); default: return Unsafe.As<T, sbyte>(ref value); } } private static T ConvertFromUInt64(ulong value) { switch (s_enumTypeCode) { case TypeCode.Int32: case TypeCode.UInt32: { uint source4 = (uint)value; return Unsafe.As<uint, T>(ref source4); } case TypeCode.Int64: case TypeCode.UInt64: { ulong source3 = value; return Unsafe.As<ulong, T>(ref source3); } case TypeCode.Int16: case TypeCode.UInt16: { ushort source2 = (ushort)value; return Unsafe.As<ushort, T>(ref source2); } default: { byte source = (byte)value; return Unsafe.As<byte, T>(ref source); } } } private unsafe string FormatEnumAsString(ulong key, T value, JsonNamingPolicy dictionaryKeyPolicy) { EnumFieldInfo[] enumFieldInfo; if (s_isFlagsEnum) { Span<char> initialBuffer = new Span<char>(stackalloc byte[256], 128); System.Text.ValueStringBuilder valueStringBuilder = new System.Text.ValueStringBuilder(initialBuffer); try { ulong num = key; enumFieldInfo = _enumFieldInfo; foreach (EnumFieldInfo enumFieldInfo2 in enumFieldInfo) { ulong key2 = enumFieldInfo2.Key; if ((key2 == 0) ? (key == 0) : ((num & key2) == key2)) { num &= ~key2; string s = (dictionaryKeyPolicy != null) ? ResolveAndValidateJsonName(enumFieldInfo2.OriginalName, dictionaryKeyPolicy, enumFieldInfo2.Kind) : enumFieldInfo2.JsonName; if (valueStringBuilder.Length > 0) valueStringBuilder.Append(", "); valueStringBuilder.Append(s); if (num == 0) break; } } return valueStringBuilder.ToString(); } finally { valueStringBuilder.Dispose(); } } enumFieldInfo = _enumFieldInfo; foreach (EnumFieldInfo enumFieldInfo3 in enumFieldInfo) { if (enumFieldInfo3.Key == key) return ResolveAndValidateJsonName(enumFieldInfo3.OriginalName, dictionaryKeyPolicy, enumFieldInfo3.Kind); } return null; } private bool IsDefinedValueOrCombinationOfValues(ulong key) { EnumFieldInfo[] enumFieldInfo; if (s_isFlagsEnum) { ulong num = key; enumFieldInfo = _enumFieldInfo; for (int i = 0; i < enumFieldInfo.Length; i++) { ulong key2 = enumFieldInfo[i].Key; if ((key2 == 0) ? (key == 0) : ((num & key2) == key2)) { num &= ~key2; if (num == 0) return true; } } return false; } enumFieldInfo = _enumFieldInfo; for (int i = 0; i < enumFieldInfo.Length; i++) { if (enumFieldInfo[i].Key == key) return true; } return false; } internal override JsonSchema GetSchema(JsonNumberHandling numberHandling) { if ((_converterOptions & EnumConverterOptions.AllowStrings) != 0) { if (s_isFlagsEnum) return new JsonSchema { Type = JsonSchemaType.String }; JsonArray jsonArray = new JsonArray((JsonNodeOptions?)null); EnumFieldInfo[] enumFieldInfo = _enumFieldInfo; foreach (EnumFieldInfo enumFieldInfo2 in enumFieldInfo) { jsonArray.Add((JsonNode)enumFieldInfo2.JsonName); } return new JsonSchema { Enum = jsonArray }; } return new JsonSchema { Type = JsonSchemaType.Integer }; } private static EnumFieldInfo[] ResolveEnumFields(JsonNamingPolicy namingPolicy) { string[] names = Enum.GetNames(typeof(T)); T[] array = (T[])Enum.GetValues(typeof(T)); Dictionary<string, string> dictionary = null; FieldInfo[] fields = typeof(T).GetFields(BindingFlags.Static | BindingFlags.Public); foreach (FieldInfo fieldInfo in fields) { JsonStringEnumMemberNameAttribute customAttribute = CustomAttributeExtensions.GetCustomAttribute<JsonStringEnumMemberNameAttribute>((MemberInfo)fieldInfo); if (customAttribute != null) (dictionary ?? (dictionary = new Dictionary<string, string>(StringComparer.Ordinal))).Add(fieldInfo.Name, customAttribute.Name); } EnumFieldInfo[] array2 = new EnumFieldInfo[names.Length]; for (int j = 0; j < names.Length; j++) { string text = names[j]; ulong key = ConvertToUInt64(array[j]); EnumFieldNameKind kind; if (dictionary != null && dictionary.TryGetValue(text, out string value)) { text = value; kind = EnumFieldNameKind.Attribute; } else kind = ((namingPolicy != null) ? EnumFieldNameKind.NamingPolicy : EnumFieldNameKind.Default); string jsonName = ResolveAndValidateJsonName(text, namingPolicy, kind); array2[j] = new EnumFieldInfo(key, kind, text, jsonName); } return array2; } private static string ResolveAndValidateJsonName(string name, JsonNamingPolicy namingPolicy, EnumFieldNameKind kind) { if (kind != EnumFieldNameKind.Attribute && namingPolicy != null) name = namingPolicy.ConvertName(name); if (string.IsNullOrEmpty(name) || char.IsWhiteSpace(name[0]) || char.IsWhiteSpace(name[name.Length - 1]) || (s_isFlagsEnum && MemoryExtensions.IndexOf<char>(name.AsSpan(), ',') >= 0)) ThrowHelper.ThrowInvalidOperationException_UnsupportedEnumIdentifier(typeof(T), name); return name; } } }