using System.Collections;
using System.Collections.Generic;
using System.Runtime.Serialization.Formatters;
using System.ComponentModel.DataAnnotations;
using System.Globalization;
using System.Collections.ObjectModel;
using System.Runtime.CompilerServices;
using System.Diagnostics;
using System.Runtime.Serialization;
using System.Text.Json.Serialization;
public class JsonPropertyNameStringEnumConverter : GeneralJsonStringEnumConverter
public JsonPropertyNameStringEnumConverter() : base() { }
public JsonPropertyNameStringEnumConverter(JsonNamingPolicy? namingPolicy = default, bool allowIntegerValues = true) : base(namingPolicy, allowIntegerValues) { }
protected override bool TryOverrideName(Type enumType, string name, out ReadOnlyMemory<char> overrideName)
if (EnumExtensions.TryGetEnumAttribute<JsonPropertyNameAttribute>(enumType, name, out var attr) && attr.Name != null)
overrideName = attr.Name.AsMemory();
return base.TryOverrideName(enumType, name, out overrideName);
public class JsonEnumMemberStringEnumConverter : GeneralJsonStringEnumConverter
public JsonEnumMemberStringEnumConverter() : base() { }
public JsonEnumMemberStringEnumConverter(JsonNamingPolicy? namingPolicy = default, bool allowIntegerValues = true) : base(namingPolicy, allowIntegerValues) { }
protected override bool TryOverrideName(Type enumType, string name, out ReadOnlyMemory<char> overrideName)
if (EnumExtensions.TryGetEnumAttribute<System.Runtime.Serialization.EnumMemberAttribute>(enumType, name, out var attr) && attr.Value != null)
overrideName = attr.Value.AsMemory();
return base.TryOverrideName(enumType, name, out overrideName);
public class GeneralJsonStringEnumConverter : JsonConverterFactory
readonly JsonNamingPolicy? namingPolicy;
readonly bool allowIntegerValues;
public GeneralJsonStringEnumConverter() : this(null, true) { }
public GeneralJsonStringEnumConverter(JsonNamingPolicy? namingPolicy = default, bool allowIntegerValues = true) => (this.namingPolicy, this.allowIntegerValues) = (namingPolicy, allowIntegerValues);
public override bool CanConvert(Type typeToConvert) => typeToConvert.IsEnum || Nullable.GetUnderlyingType(typeToConvert)?.IsEnum == true;
public sealed override JsonConverter CreateConverter(Type typeToConvert, JsonSerializerOptions options)
var enumType = Nullable.GetUnderlyingType(typeToConvert) ?? typeToConvert;
JsonConverter enumConverter;
TryOverrideName tryOverrideName = (Type t, string n, out ReadOnlyMemory<char> o) => TryOverrideName(t, n, out o);
var converterType = typeof(JsonEnumConverter<>).MakeGenericType(new [] {enumType});
enumConverter = (JsonConverter)Activator.CreateInstance(converterType,
BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic,
args: new object[] { namingPolicy!, allowIntegerValues, tryOverrideName },
if (enumType == typeToConvert)
var nullableConverter = (JsonConverter)Activator.CreateInstance(typeof(NullableConverterDecorator<>).MakeGenericType(new [] {enumType}),
BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic,
args: new object[] { enumConverter },
return nullableConverter;
protected virtual bool TryOverrideName(Type enumType, string name, out ReadOnlyMemory<char> overrideName)
class JsonEnumConverter<TEnum> : JsonConverter<TEnum> where TEnum: struct, Enum
protected static TypeCode EnumTypeCode { get; } = Type.GetTypeCode(typeof(TEnum));
private bool AllowNumbers { get; }
EnumTypeData<TEnum> EnumTypeData { get; }
public JsonEnumConverter(JsonNamingPolicy? namingPolicy, bool allowNumbers, TryOverrideName? tryOverrideName)
this.AllowNumbers = allowNumbers;
this.EnumTypeData = EnumTypeData<TEnum>.Create(namingPolicy, tryOverrideName);
public sealed override void Write(Utf8JsonWriter writer, TEnum value, JsonSerializerOptions options)
if (EnumTypeData.TryFormatAsString(value, out var name))
writer.WriteStringValue(name.Span);
throw new JsonException();
WriteEnumAsNumber(writer, value);
public sealed override TEnum Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) =>
JsonTokenType.String => EnumTypeData.TryLookup(reader.GetString().AsMemory(), out var value) ? value : throw new JsonException(),
JsonTokenType.Number => AllowNumbers ? ReadNumberAsEnum(ref reader) : throw new JsonException(),
_ => throw new JsonException(),
static void WriteEnumAsNumber(Utf8JsonWriter writer, TEnum value)
writer.WriteNumberValue(Unsafe.As<TEnum, SByte>(ref value));
writer.WriteNumberValue(Unsafe.As<TEnum, Int16>(ref value));
writer.WriteNumberValue(Unsafe.As<TEnum, Int32>(ref value));
writer.WriteNumberValue(Unsafe.As<TEnum, Int64>(ref value));
writer.WriteNumberValue(Unsafe.As<TEnum, Byte>(ref value));
writer.WriteNumberValue(Unsafe.As<TEnum, UInt16>(ref value));
writer.WriteNumberValue(Unsafe.As<TEnum, UInt32>(ref value));
writer.WriteNumberValue(Unsafe.As<TEnum, UInt64>(ref value));
throw new JsonException();
static TEnum ReadNumberAsEnum(ref Utf8JsonReader reader)
var i = reader.GetSByte();
return Unsafe.As<SByte, TEnum>(ref i);
var i = reader.GetInt16();
return Unsafe.As<Int16, TEnum>(ref i);
var i = reader.GetInt32();
return Unsafe.As<Int32, TEnum>(ref i);
var i = reader.GetInt64();
return Unsafe.As<Int64, TEnum>(ref i);
var i = reader.GetByte();
return Unsafe.As<Byte, TEnum>(ref i);
var i = reader.GetUInt16();
return Unsafe.As<UInt16, TEnum>(ref i);
var i = reader.GetUInt32();
return Unsafe.As<UInt32, TEnum>(ref i);
var i = reader.GetUInt64();
return Unsafe.As<UInt64, TEnum>(ref i);
throw new JsonException();
public sealed class NullableConverterDecorator<T> : JsonConverter<T?> where T : struct
readonly JsonConverter<T> innerConverter;
public NullableConverterDecorator(JsonConverter<T> innerConverter) => this.innerConverter = innerConverter ?? throw new ArgumentNullException(nameof(innerConverter));
public override T? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) => innerConverter.Read(ref reader, Nullable.GetUnderlyingType(typeToConvert)!, options);
public override void Write(Utf8JsonWriter writer, T? value, JsonSerializerOptions options) => innerConverter.Write(writer, value!.Value, options);
public override bool CanConvert(Type type) => base.CanConvert(type) && innerConverter.CanConvert(Nullable.GetUnderlyingType(type)!);
public delegate bool TryOverrideName(Type enumType, string name, out ReadOnlyMemory<char> overrideName);
public class EnumTypeData<TEnum> where TEnum : struct, Enum
readonly record struct EnumData(ReadOnlyMemory<char> name, TEnum value, UInt64 UInt64Value);
internal static TypeCode EnumTypeCode { get; } = Type.GetTypeCode(typeof(TEnum));
static Func<EnumData, UInt64, int> EntryComparer { get; } = (item, key) => item.UInt64Value.CompareTo(key);
static bool Flagged { get; } = typeof(TEnum).IsDefined(typeof(FlagsAttribute), false);
private EnumData [] EnumDataArray { get; }
private ILookup<ReadOnlyMemory<char>, int> NameLookup { get; }
private EnumTypeData(EnumData [] enumDataArray, ILookup<ReadOnlyMemory<char>, int> nameLookup) =>
(this.EnumDataArray, this.NameLookup) = (enumDataArray ?? throw new ArgumentNullException(nameof(enumDataArray)), nameLookup ?? throw new ArgumentNullException(nameof(nameLookup)));
public static EnumTypeData<TEnum> Create(JsonNamingPolicy? namingPolicy, TryOverrideName? tryOverrideName)
var enumData = GetEnumData(namingPolicy, tryOverrideName);
return new EnumTypeData<TEnum>(enumData, GetLookupTable(enumData));
static EnumData [] GetEnumData(JsonNamingPolicy? namingPolicy, TryOverrideName? tryOverrideName)
var names = Enum.GetNames<TEnum>();
var values = Enum.GetValues<TEnum>();
return names.Zip(values, (n, v) =>
if (tryOverrideName == null || !tryOverrideName(typeof(TEnum), n, out var jsonName))
jsonName = (namingPolicy == null ? n.AsMemory() : namingPolicy.ConvertName(n).AsMemory());
return new EnumData(jsonName, v, v.ToUInt64(EnumTypeCode));
static int FindEnumDataIndex(EnumData [] enumData, TEnum value, out UInt64 UInt64value)
UInt64value = value.ToUInt64(EnumTypeCode);
return enumData.BinarySearchFirst(UInt64value, EntryComparer);
static ILookup<ReadOnlyMemory<char>, int> GetLookupTable(EnumData [] namesAndValues) =>
Enumerable.Range(0, namesAndValues.Length).ToLookup(i => namesAndValues[i].name, CharMemoryComparer.OrdinalIgnoreCase);
public bool TryFormatAsString(TEnum value, out ReadOnlyMemory<char> name)
return TryFormatSimpleAsString(value, out name);
return TryFormatFlaggedAsString(value, out name);
bool TryFormatSimpleAsString(TEnum value, out ReadOnlyMemory<char> name)
var index = FindEnumDataIndex(EnumDataArray, value, out var _);
name = EnumDataArray[index].name;
bool TryFormatFlaggedAsString(TEnum value, out ReadOnlyMemory<char> name)
var index = FindEnumDataIndex(EnumDataArray, value, out var UInt64Value);
name = EnumDataArray[index].name;
StringBuilder? sb = null;
for (int i = (~index) - 1; i >= 0; i--)
if ((UInt64Value & EnumDataArray[i].UInt64Value) == EnumDataArray[i].UInt64Value && EnumDataArray[i].UInt64Value != 0)
sb = new StringBuilder();
sb.Append(EnumDataArray[i].name.Span);
sb.Insert(0, EnumExtensions.FlagSeparatorString);
sb.Insert(0, EnumDataArray[i].name.Span);
UInt64Value -= EnumDataArray[i].UInt64Value;
if (UInt64Value == 0 && sb != null)
name = sb.ToString().AsMemory();
public bool TryLookup(ReadOnlyMemory<char> name, out TEnum value)
return TryLookupSimple(name, out value);
return TryLookupFlagged(name, out value);
bool TryLookupSimple(ReadOnlyMemory<char> name, out TEnum value)
foreach (var index in NameLookup[name])
if (i == 1 && MemoryExtensions.Equals(EnumDataArray[firstMatch].name.Span, name.Span, StringComparison.Ordinal))
value = EnumDataArray[firstMatch].value;
if (MemoryExtensions.Equals(EnumDataArray[index].name.Span, name.Span, StringComparison.Ordinal))
value = EnumDataArray[index].value;
value = (firstMatch == -1 ? default : EnumDataArray[firstMatch].value);
bool TryLookupFlagged(ReadOnlyMemory<char> name, out TEnum value)
foreach (var slice in name.Split(EnumExtensions.FlagSeparatorChar, StringSplitOptions.TrimEntries))
if (TryLookupSimple(slice, out var thisValue))
UInt64Value |= thisValue.ToUInt64();
value = UInt64Value.ToEnum<TEnum>(EnumTypeCode);
public static partial class EnumExtensions
public const char FlagSeparatorChar = ',';
public const string FlagSeparatorString = ", ";
public static bool TryGetEnumAttribute<TAttribute>(Type type, string name, [System.Diagnostics.CodeAnalysis.NotNullWhen(returnValue: true)] out TAttribute? attribute) where TAttribute : System.Attribute
var member = type.GetMember(name).SingleOrDefault();
attribute = member?.GetCustomAttribute<TAttribute>(false);
return attribute != null;
public static UInt64 ToUInt64<TEnum>(this TEnum value) where TEnum : struct, Enum => value.ToUInt64(EnumTypeData<TEnum>.EnumTypeCode);
internal static UInt64 ToUInt64<TEnum>(this TEnum value, TypeCode enumTypeCode) where TEnum : struct, Enum
Debug.Assert(enumTypeCode == Type.GetTypeCode(typeof(TEnum)));
return enumTypeCode switch
TypeCode.SByte => unchecked((ulong)Unsafe.As<TEnum, SByte>(ref value)),
TypeCode.Int16 => unchecked((ulong)Unsafe.As<TEnum, Int16>(ref value)),
TypeCode.Int32 => unchecked((ulong)Unsafe.As<TEnum, Int32>(ref value)),
TypeCode.Int64 => unchecked((ulong)Unsafe.As<TEnum, Int64>(ref value)),
TypeCode.Byte => Unsafe.As<TEnum, Byte>(ref value),
TypeCode.UInt16 => Unsafe.As<TEnum, UInt16>(ref value),
TypeCode.UInt32 => Unsafe.As<TEnum, UInt32>(ref value),
TypeCode.UInt64 => Unsafe.As<TEnum, UInt64>(ref value),
_ => throw new ArgumentException(enumTypeCode.ToString()),
public static TEnum ToEnum<TEnum>(this UInt64 value) where TEnum : struct, Enum => value.ToEnum<TEnum>(EnumTypeData<TEnum>.EnumTypeCode);
internal static TEnum ToEnum<TEnum>(this UInt64 value, TypeCode enumTypeCode) where TEnum : struct, Enum
Debug.Assert(enumTypeCode == Type.GetTypeCode(typeof(TEnum)));
var i = unchecked((SByte)value);
return Unsafe.As<SByte, TEnum>(ref i);
var i = unchecked((Int16)value);
return Unsafe.As<Int16, TEnum>(ref i);
var i = unchecked((Int32)value);
return Unsafe.As<Int32, TEnum>(ref i);
var i = unchecked((Int64)value);
return Unsafe.As<Int64, TEnum>(ref i);
var i = unchecked((Byte)value);
return Unsafe.As<Byte, TEnum>(ref i);
var i = unchecked((UInt16)value);
return Unsafe.As<UInt16, TEnum>(ref i);
var i = unchecked((UInt32)value);
return Unsafe.As<UInt32, TEnum>(ref i);
var i = unchecked((UInt64)value);
return Unsafe.As<UInt64, TEnum>(ref i);
throw new ArgumentException(enumTypeCode.ToString());
public static class StringExtensions
public static IEnumerable<ReadOnlyMemory<char>> Split(this ReadOnlyMemory<char> chars, char separator, StringSplitOptions options = StringSplitOptions.None)
while ((index = chars.Span.IndexOf(separator)) >= 0)
var slice = chars.Slice(0, index);
if ((options & StringSplitOptions.TrimEntries) == StringSplitOptions.TrimEntries)
if ((options & StringSplitOptions.RemoveEmptyEntries) == 0 || slice.Length > 0)
chars = chars.Slice(index + 1);
if ((options & StringSplitOptions.TrimEntries) == StringSplitOptions.TrimEntries)
if ((options & StringSplitOptions.RemoveEmptyEntries) == 0 || chars.Length > 0)
public static class ListExtensions
public static int BinarySearch<TValue, TKey>(this TValue [] list, TKey key, Func<TValue, TKey, int> comparer)
if (list == null || comparer == null)
throw new ArgumentNullException();
int high = list.Length - 1;
var mid = low + ((high - low) >> 1);
var order = comparer(list[mid], key);
public static int BinarySearchFirst<TValue, TKey>(this TValue [] list, TKey key, Func<TValue, TKey, int> comparer)
int index = list.BinarySearch(key, comparer);
for (; index > 0 && comparer(list[index-1], key) == 0; index--)
public class CharMemoryComparer : IEqualityComparer<ReadOnlyMemory<char>>
public static CharMemoryComparer OrdinalIgnoreCase { get; } = new CharMemoryComparer(StringComparison.OrdinalIgnoreCase);
public static CharMemoryComparer Ordinal { get; } = new CharMemoryComparer(StringComparison.Ordinal);
readonly StringComparison comparison;
CharMemoryComparer(StringComparison comparison) => this.comparison = comparison;
public bool Equals(ReadOnlyMemory<char> x, ReadOnlyMemory<char> y) => MemoryExtensions.Equals(x.Span, y.Span, comparison);
public int GetHashCode(ReadOnlyMemory<char> obj) => String.GetHashCode(obj.Span, comparison);
[JsonPropertyName("Trick-Or-Treat")]
Fourteen = ((long)1 << 14),
ThirtyOne = ((long)1 << 31),
ThirtyTwo = ((long)1 << 32),
SixtyTwo = ((long)1 << 62),
SixtyThree = ((long)1 << 63),
Fourteen = ((ulong)1 << 14),
ThirtyOne = ((ulong)1 << 31),
ThirtyTwo = ((ulong)1 << 32),
ThirtyThree = ((ulong)1 << 33),
Sixty = ((ulong)1 << 60),
SixtyOne = ((ulong)1 << 61),
SixtyTwo = ((ulong)1 << 62),
SixtyThree = ((ulong)1 << 63),
public enum DuplicateEnum : short
public enum EnumDifferingInCase : short { AA, Aa, aa }
public enum EmptyEnum { }
public enum FlaggedEmptyEnum { }
public enum FlaggedWithComposite
All = One | Two | Four | Eight,
Fourteen = ((long)1 << 14),
ThirtyOne = ((long)1 << 31),
ThirtyTwo = ((long)1 << 32),
SixtyTwo = ((long)1 << 62),
SixtyThree = ((long)1 << 63),
enum FlagULongEnum : ulong
Fourteen = ((ulong)1 << 14),
ThirtyOne = ((ulong)1 << 31),
ThirtyTwo = ((ulong)1 << 32),
ThirtyThree = ((ulong)1 << 33),
Sixty = ((ulong)1 << 60),
SixtyOne = ((ulong)1 << 61),
SixtyTwo = ((ulong)1 << 62),
SixtyThree = ((ulong)1 << 63),
enum FlagsWithManyDuplicates
public enum EnumMemberExample {
[EnumMember(Value = "Trick-Or-Treat")]
public class WackyNamingPolicy : JsonNamingPolicy
public override string ConvertName(string name) => "Wacky-" + name;
public static void Test()
TestEnumMemberOverride();
Assert.AreEqual(TypeCode.Int64, Type.GetTypeCode(typeof(LongEnum)));
var sysOptions = new JsonSerializerOptions
Converters = { new JsonStringEnumConverter() },
Assert.AreEqual("[\"One\",\"One\"]", JsonSerializer.Serialize(Enum.GetValues<DuplicateEnum>(), sysOptions));
Assert.AreEqual("[\"AA\",\"Aa\",\"aa\"]", JsonSerializer.Serialize(Enum.GetValues<EnumDifferingInCase>(), sysOptions));
Assert.AreEqual(EnumDifferingInCase.AA, JsonSerializer.Deserialize<EnumDifferingInCase>("\"AA\"", sysOptions));
Assert.AreEqual(EnumDifferingInCase.aa, JsonSerializer.Deserialize<EnumDifferingInCase>("\"aa\"", sysOptions));
Assert.AreEqual(EnumDifferingInCase.Aa, JsonSerializer.Deserialize<EnumDifferingInCase>("\"Aa\"", sysOptions));
Assert.AreEqual(EnumDifferingInCase.AA, JsonSerializer.Deserialize<EnumDifferingInCase>("\"aA\"", sysOptions));
static void TestEnumMemberOverride()
var myOptions = new JsonSerializerOptions
Converters = { new JsonEnumMemberStringEnumConverter(null, true) },
var test = Enum.GetValues<EnumMemberExample>();
var json = JsonSerializer.Serialize(test, myOptions);
Assert.AreEqual("[\"Trick\",\"Treat\",\"Trick-Or-Treat\"]", json);
var myOptions2 = new JsonSerializerOptions
Converters = { new JsonEnumMemberStringEnumConverter(JsonNamingPolicy.CamelCase, true) },
var json2 = JsonSerializer.Serialize(test, myOptions2);
Assert.AreEqual("[\"trick\",\"treat\",\"Trick-Or-Treat\"]", json2);
Console.WriteLine(json2);
static void TestOverride()
var myOptions = new JsonSerializerOptions
Converters = { new JsonPropertyNameStringEnumConverter(null, true) },
var test = Enum.GetValues<Example>();
var json = JsonSerializer.Serialize(test, myOptions);
Assert.AreEqual("[\"Trick\",\"Treat\",\"Trick-Or-Treat\"]", json);
var myOptions2 = new JsonSerializerOptions
Converters = { new JsonPropertyNameStringEnumConverter(JsonNamingPolicy.CamelCase, true) },
var json2 = JsonSerializer.Serialize(test, myOptions2);
Assert.AreEqual("[\"trick\",\"treat\",\"Trick-Or-Treat\"]", json2);
Console.WriteLine(json2);
static void TestNullable()
TestNullableWithPolicy();
static void TestNullableNoPolicy()
var myOptions = new JsonSerializerOptions
Converters = { new JsonPropertyNameStringEnumConverter(null, true) },
var json = "[null,\"Zero\",\"SixtyThree\",\"One, Two\"]";
var array = JsonSerializer.Deserialize<FlagULongEnum? []>(json, myOptions);
Assert.IsTrue(array!.SequenceEqual(new [] { (FlagULongEnum?)null, FlagULongEnum.Zero, FlagULongEnum.SixtyThree, FlagULongEnum.One | FlagULongEnum.Two }));
Assert.AreEqual(json, JsonSerializer.Serialize(array, myOptions), json);
static void TestNullableWithPolicy()
var myOptions = new JsonSerializerOptions
Converters = { new GeneralJsonStringEnumConverter(JsonNamingPolicy.CamelCase, true) },
var json = "[null,\"zero\",\"sixtyThree\",\"one, two\"]";
var array = JsonSerializer.Deserialize<FlagULongEnum? []>(json, myOptions);
Assert.IsTrue(array!.SequenceEqual(new [] { (FlagULongEnum?)null, FlagULongEnum.Zero, FlagULongEnum.SixtyThree, FlagULongEnum.One | FlagULongEnum.Two }));
Assert.AreEqual(json, JsonSerializer.Serialize(array, myOptions), json);
Assert.AreEqual(4, ",,,".AsMemory().Split(',').Count());
Assert.AreEqual(0, ",,,".AsMemory().Split(',', StringSplitOptions.RemoveEmptyEntries).Count());
Assert.AreEqual(0, " , , , ".AsMemory().Split(',', StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries).Count());
Assert.AreEqual(4, " , , , ".AsMemory().Split(',', StringSplitOptions.RemoveEmptyEntries).Count());
static void TestFlagged()
var sysOptions = new JsonSerializerOptions
Converters = { new JsonStringEnumConverter(new WackyNamingPolicy()) },
var test = Enum.GetValues<FlaggedWithComposite>().Concat(new [] { FlaggedWithComposite.Four | FlaggedWithComposite.Two }).ToArray();
TestWriteArrayFlagged(test, null);
TestWriteArrayFlagged(new [] { default(FlaggedEmptyEnum) });
TestRoundTripFlagged<FlaggedEmptyEnum>();
TestRoundTripFlagged<FlaggedWithComposite>(test);
TestRoundTripFlagged<FlagLongEnum>();
TestRoundTripFlagged<FlagULongEnum>();
TestRoundTripFlagged<FlagsWithManyDuplicates>();
TestRoundTripWithNamingPolicyFlagged(Enum.GetValues<FlagLongEnum>(), new WackyNamingPolicy(), true);
TestRoundTripWithNamingPolicyFlagged(Enum.GetValues<FlagULongEnum>(), new WackyNamingPolicy(), true);
TestRoundTripWithNamingPolicyFlagged(Enum.GetValues<FlagsWithManyDuplicates>(), new WackyNamingPolicy(), true);
static void TestRoundTripFlagged<TEnum>() where TEnum : struct, Enum =>
TestRoundTripFlagged(Enum.GetValues<TEnum>());
static void TestRoundTripFlagged<TEnum>(TEnum [] values, bool print = false) where TEnum : struct, Enum
var json = TestWriteArrayFlagged(values);
TestReadArrayFlagged<TEnum>(json);
static void TestRoundTripWithNamingPolicyFlagged<TEnum>(TEnum [] array, JsonNamingPolicy? namingPolicy, bool print = false) where TEnum : struct, Enum
var json = TestWriteArrayFlagged(array, namingPolicy);
var myOptions = new JsonSerializerOptions
Converters = { new GeneralJsonStringEnumConverter(namingPolicy, true) },
var myArray = JsonSerializer.Deserialize<TEnum []>(json, myOptions);
Assert.IsTrue(array.SequenceEqual(myArray!), json);
static string TestWriteArrayFlagged<TEnum>(TEnum [] array, JsonNamingPolicy? namingPolicy = default) where TEnum : struct, Enum
var sysOptions = new JsonSerializerOptions
Converters = { new JsonStringEnumConverter(namingPolicy) },
var myOptions = new JsonSerializerOptions
Converters = { new GeneralJsonStringEnumConverter(namingPolicy, true) },
var sysJson = JsonSerializer.Serialize(array, sysOptions);
var myJson = JsonSerializer.Serialize(array, myOptions);
Assert.AreEqual(sysJson, myJson, myJson);
static void TestReadArrayFlagged<TEnum>(string arrayJson) where TEnum : struct, Enum
var sysOptions = new JsonSerializerOptions
Converters = { new JsonStringEnumConverter() },
var myOptions = new JsonSerializerOptions
Converters = { new GeneralJsonStringEnumConverter(null, true) },
var sysArray = JsonSerializer.Deserialize<TEnum []>(arrayJson, sysOptions);
var myArray = JsonSerializer.Deserialize<TEnum []>(arrayJson, myOptions);
Assert.IsTrue(sysArray!.SequenceEqual(myArray!));
static void TestUnflagged()
TestRoundTrip(Enumerable.Range(-4, 20).Select(i => (LongEnum)i).ToArray());
TestRoundTripWithNamingPolicy(new [] { default(EmptyEnum), (EmptyEnum)1 }, new WackyNamingPolicy(), false);
TestRoundTrip<ByteEnum>();
TestRoundTrip<SByteEnum>();
TestRoundTrip<Int16Enum>();
TestRoundTrip<UInt16Enum>();
TestRoundTrip<Int32Enum>();
TestRoundTrip<UInt32Enum>();
TestRoundTrip<LongEnum>();
TestRoundTrip<ULongEnum>();
TestRoundTrip<DuplicateEnum>();
TestRoundTrip<EnumDifferingInCase>();
TestReadArray<EnumDifferingInCase>("[\"AA\", \"aA\", \"Aa\", \"aa\"]");
TestRoundTripWithNamingPolicy(Enum.GetValues<ULongEnum>(), new WackyNamingPolicy(), true);
TestRoundTripWithNamingPolicy(Enum.GetValues<EnumDifferingInCase>(), new WackyNamingPolicy(), true);
TestWriteArray(Enum.GetValues<EnumDifferingInCase>(), new WackyNamingPolicy());
TestRoundTripWithNamingPolicy(Enumerable.Range(-4, 20).Select(i => (LongEnum)i).ToArray(), null, true);
TestRoundTripWithNamingPolicy(Enumerable.Range(-4, 20).Select(i => (LongEnum)i).ToArray(), new WackyNamingPolicy(), false);
TestRoundTripWithNamingPolicy(Enumerable.Range(-4, 20).Select(i => (ULongEnum)i).ToArray(), new WackyNamingPolicy(), false);
static void TestRoundTrip<TEnum>() where TEnum : struct, Enum =>
TestRoundTrip(Enum.GetValues<TEnum>());
static void TestRoundTrip<TEnum>(TEnum [] values, bool print = false) where TEnum : struct, Enum
var json = TestWriteArray(values);
TestReadArray<TEnum>(json);
static void TestRoundTripWithNamingPolicy<TEnum>(TEnum [] array, JsonNamingPolicy? namingPolicy, bool print = false) where TEnum : struct, Enum
var json = TestWriteArray(array, namingPolicy);
var myOptions = new JsonSerializerOptions
Converters = { new GeneralJsonStringEnumConverter(namingPolicy, true) },
var myArray = JsonSerializer.Deserialize<TEnum []>(json, myOptions);
Assert.IsTrue(array.SequenceEqual(myArray!), json);
static string TestWriteArray<TEnum>(TEnum [] array, JsonNamingPolicy? namingPolicy = default) where TEnum : struct, Enum
var sysOptions = new JsonSerializerOptions
Converters = { new JsonStringEnumConverter(namingPolicy) },
var myOptions = new JsonSerializerOptions
Converters = { new GeneralJsonStringEnumConverter(namingPolicy, true) },
var sysJson = JsonSerializer.Serialize(array, sysOptions);
var myJson = JsonSerializer.Serialize(array, myOptions);
Assert.AreEqual(sysJson, myJson, myJson);
static void TestReadArray<TEnum>(string arrayJson) where TEnum : struct, Enum
var sysOptions = new JsonSerializerOptions
Converters = { new JsonStringEnumConverter() },
var myOptions = new JsonSerializerOptions
Converters = { new GeneralJsonStringEnumConverter(null, true) },
var sysArray = JsonSerializer.Deserialize<TEnum []>(arrayJson, sysOptions);
var myArray = JsonSerializer.Deserialize<TEnum []>(arrayJson, myOptions);
Assert.IsTrue(sysArray!.SequenceEqual(myArray!));
public static void Main()
Console.WriteLine("Environment version: {0} ({1}), OS: {2}.", System.Runtime.InteropServices.RuntimeInformation.FrameworkDescription, Environment.Version, Environment.OSVersion);
Console.WriteLine("System.Text.Json version: " + typeof(JsonSerializer).Assembly.FullName);
Console.WriteLine("Failed with unhandled exception: ");