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 (JsonEnumExtensions.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 (JsonEnumExtensions.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 delegate bool TryOverrideName(Type enumType, string name, out ReadOnlyMemory<char> 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;
var flagged = enumType.IsDefined(typeof(FlagsAttribute), true);
JsonConverter enumConverter;
TryOverrideName tryOverrideName = (Type t, string n, out ReadOnlyMemory<char> o) => TryOverrideName(t, n, out o);
var converterType = (flagged ? typeof(FlaggedJsonEnumConverter<>) : typeof(UnflaggedJsonEnumConverter<>)).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 FlaggedJsonEnumConverter<TEnum> : JsonEnumConverterBase<TEnum> where TEnum: struct, Enum
private const char FlagSeparatorChar = ',';
private const string FlagSeparatorString = ", ";
public FlaggedJsonEnumConverter(JsonNamingPolicy? namingPolicy, bool allowNumbers, TryOverrideName? tryOverrideName) : base(namingPolicy, allowNumbers, tryOverrideName) { }
protected override bool TryFormatAsString(EnumData<TEnum> [] enumData, TEnum value, out ReadOnlyMemory<char> name)
UInt64 UInt64Value = JsonEnumExtensions.ToUInt64(value, EnumTypeCode);
var index = enumData.BinarySearchFirst(UInt64Value, EntryComparer);
name = enumData[index].name;
StringBuilder? sb = null;
for (int i = (~index) - 1; i >= 0; i--)
if ((UInt64Value & enumData[i].UInt64Value) == enumData[i].UInt64Value && enumData[i].UInt64Value != 0)
sb = new StringBuilder();
sb.Append(enumData[i].name.Span);
sb.Insert(0, FlagSeparatorString);
sb.Insert(0, enumData[i].name.Span);
UInt64Value -= enumData[i].UInt64Value;
if (UInt64Value == 0 && sb != null)
name = sb.ToString().AsMemory();
protected override bool TryReadAsString(EnumData<TEnum> [] enumData, ILookup<ReadOnlyMemory<char>, int> nameLookup, ReadOnlyMemory<char> name, out TEnum value)
foreach (var slice in name.Split(FlagSeparatorChar, StringSplitOptions.TrimEntries))
if (JsonEnumExtensions.TryLookupBest<TEnum>(enumData, nameLookup, slice, out TEnum thisValue))
UInt64Value |= thisValue.ToUInt64(EnumTypeCode);
value = JsonEnumExtensions.FromUInt64<TEnum>(UInt64Value);
class UnflaggedJsonEnumConverter<TEnum> : JsonEnumConverterBase<TEnum> where TEnum: struct, Enum
public UnflaggedJsonEnumConverter(JsonNamingPolicy? namingPolicy, bool allowNumbers, TryOverrideName? tryOverrideName) : base(namingPolicy, allowNumbers, tryOverrideName) { }
protected override bool TryFormatAsString(EnumData<TEnum> [] enumData, TEnum value, out ReadOnlyMemory<char> name)
var index = enumData.BinarySearchFirst(JsonEnumExtensions.ToUInt64(value, EnumTypeCode), EntryComparer);
name = enumData[index].name;
protected override bool TryReadAsString(EnumData<TEnum> [] enumData, ILookup<ReadOnlyMemory<char>, int> nameLookup, ReadOnlyMemory<char> name, out TEnum value) =>
JsonEnumExtensions.TryLookupBest(enumData, nameLookup, name, out value);
abstract class JsonEnumConverterBase<TEnum> : JsonConverter<TEnum> where TEnum: struct, Enum
protected static TypeCode EnumTypeCode { get; } = Type.GetTypeCode(typeof(TEnum));
protected static Func<EnumData<TEnum>, UInt64, int> EntryComparer { get; } = (item, key) => item.UInt64Value.CompareTo(key);
private bool AllowNumbers { get; }
private EnumData<TEnum> [] EnumData { get; }
private ILookup<ReadOnlyMemory<char>, int> NameLookup { get; }
public JsonEnumConverterBase(JsonNamingPolicy? namingPolicy, bool allowNumbers, TryOverrideName? tryOverrideName)
this.AllowNumbers = allowNumbers;
this.EnumData = JsonEnumExtensions.GetData<TEnum>(namingPolicy, tryOverrideName).ToArray();
this.NameLookup = JsonEnumExtensions.GetLookupTable<TEnum>(this.EnumData);
public sealed override void Write(Utf8JsonWriter writer, TEnum value, JsonSerializerOptions options)
if (TryFormatAsString(EnumData, value, out var name))
writer.WriteStringValue(name.Span);
throw new JsonException();
WriteEnumAsNumber(writer, value);
protected abstract bool TryFormatAsString(EnumData<TEnum> [] enumData, TEnum value, out ReadOnlyMemory<char> name);
protected abstract bool TryReadAsString(EnumData<TEnum> [] enumData, ILookup<ReadOnlyMemory<char>, int> nameLookup, ReadOnlyMemory<char> name, out TEnum value);
public sealed override TEnum Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) =>
JsonTokenType.String => TryReadAsString(EnumData, NameLookup, 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)!);
internal readonly record struct EnumData<TEnum>(ReadOnlyMemory<char> name, TEnum value, UInt64 UInt64Value) where TEnum : struct, Enum;
internal static class JsonEnumExtensions
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(Type.GetTypeCode(typeof(TEnum)));
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 FromUInt64<TEnum>(this UInt64 value) where TEnum : struct, Enum => value.FromUInt64<TEnum>(Type.GetTypeCode(typeof(TEnum)));
internal static TEnum FromUInt64<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());
internal static IEnumerable<EnumData<TEnum>> GetData<TEnum>(JsonNamingPolicy? namingPolicy, TryOverrideName? tryOverrideName) where TEnum : struct, Enum =>
GetData<TEnum>(namingPolicy, tryOverrideName, Type.GetTypeCode(typeof(TEnum)));
internal static IEnumerable<EnumData<TEnum>> GetData<TEnum>(JsonNamingPolicy? namingPolicy, TryOverrideName? tryOverrideName, TypeCode enumTypeCode) where TEnum : struct, Enum
Debug.Assert(enumTypeCode == Type.GetTypeCode(typeof(TEnum)));
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<TEnum>(jsonName, v, v.ToUInt64(enumTypeCode));
internal static ILookup<ReadOnlyMemory<char>, int> GetLookupTable<TEnum>(EnumData<TEnum> [] namesAndValues) where TEnum : struct, Enum =>
Enumerable.Range(0, namesAndValues.Length).ToLookup(i => namesAndValues[i].name, CharMemoryComparer.OrdinalIgnoreCase);
internal static bool TryLookupBest<TEnum>(EnumData<TEnum> [] namesAndValues, ILookup<ReadOnlyMemory<char>, int> lookupTable, ReadOnlyMemory<char> name, out TEnum value) where TEnum : struct, Enum
foreach (var index in lookupTable[name])
if (i == 1 && MemoryExtensions.Equals(namesAndValues[firstMatch].name.Span, name.Span, StringComparison.Ordinal))
value = namesAndValues[firstMatch].value;
if (MemoryExtensions.Equals(namesAndValues[index].name.Span, name.Span, StringComparison.Ordinal))
value = namesAndValues[index].value;
value = (firstMatch == -1 ? default : namesAndValues[firstMatch].value);
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: ");