From: Jeremy Kuhne Date: Thu, 20 Jun 2019 19:07:20 +0000 (-0700) Subject: String enum converter (dotnet/corefx#38702) X-Git-Tag: submit/tizen/20210909.063632~11031^2~1215 X-Git-Url: http://review.tizen.org/git/?a=commitdiff_plain;h=04872b04c07ff70708617fa2377df23f6704018e;p=platform%2Fupstream%2Fdotnet%2Fruntime.git String enum converter (dotnet/corefx#38702) * String enum converter Adds public converter for converting enums to strings and vice-versa. * Address feedback. * Missing a readonly * Fix exception and swtich to ConcurrentDictionary * Merge fixup Commit migrated from https://github.com/dotnet/corefx/commit/66a18944db6d64b67adb1320d41004a9140babc1 --- diff --git a/src/libraries/System.Text.Json/ref/System.Text.Json.cs b/src/libraries/System.Text.Json/ref/System.Text.Json.cs index e1f0bc5..3c0482c 100644 --- a/src/libraries/System.Text.Json/ref/System.Text.Json.cs +++ b/src/libraries/System.Text.Json/ref/System.Text.Json.cs @@ -442,6 +442,12 @@ namespace System.Text.Json.Serialization public virtual void Write(System.Text.Json.Utf8JsonWriter writer, T value, System.Text.Json.JsonEncodedText propertyName, System.Text.Json.JsonSerializerOptions options) { } public abstract void Write(System.Text.Json.Utf8JsonWriter writer, T value, System.Text.Json.JsonSerializerOptions options); } + public sealed class JsonStringEnumConverter : System.Text.Json.Serialization.JsonConverterFactory + { + public JsonStringEnumConverter(System.Text.Json.JsonNamingPolicy namingPolicy = null, bool allowIntegerValues = true) { } + public override bool CanConvert(System.Type type) { throw null; } + protected override System.Text.Json.Serialization.JsonConverter CreateConverter(System.Type type) { throw null; } + } [System.AttributeUsageAttribute(System.AttributeTargets.Property, AllowMultiple=false)] public sealed partial class JsonExtensionDataAttribute : System.Text.Json.Serialization.JsonAttribute { diff --git a/src/libraries/System.Text.Json/src/System.Text.Json.csproj b/src/libraries/System.Text.Json/src/System.Text.Json.csproj index 9298c9f..6160807 100644 --- a/src/libraries/System.Text.Json/src/System.Text.Json.csproj +++ b/src/libraries/System.Text.Json/src/System.Text.Json.csproj @@ -23,6 +23,9 @@ + + + @@ -55,7 +58,6 @@ - @@ -65,6 +67,7 @@ + diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/EnumConverterOptions.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/EnumConverterOptions.cs new file mode 100644 index 0000000..9a12bcb --- /dev/null +++ b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/EnumConverterOptions.cs @@ -0,0 +1,20 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +namespace System.Text.Json.Serialization.Converters +{ + [Flags] + internal enum EnumConverterOptions + { + /// + /// Allow string values. + /// + AllowStrings = 0b0001, + + /// + /// Allow number values. + /// + AllowNumbers = 0b0010 + } +} diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/JsonEnumConverter.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/JsonConverterEnum.cs similarity index 81% rename from src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/JsonEnumConverter.cs rename to src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/JsonConverterEnum.cs index 073fe4c..aada8d1 100644 --- a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/JsonEnumConverter.cs +++ b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/JsonConverterEnum.cs @@ -8,13 +8,10 @@ namespace System.Text.Json.Serialization.Converters { internal sealed class JsonConverterEnum : JsonConverterFactory { - public JsonConverterEnum(bool treatAsString) + public JsonConverterEnum() { - TreatAsString = treatAsString; } - public bool TreatAsString { get; private set; } - public override bool CanConvert(Type type) { return type.IsEnum; @@ -26,7 +23,7 @@ namespace System.Text.Json.Serialization.Converters typeof(JsonConverterEnum<>).MakeGenericType(type), BindingFlags.Instance | BindingFlags.Public, binder: null, - new object[] { TreatAsString }, + new object[] { EnumConverterOptions.AllowNumbers }, culture: null); return converter; diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/JsonValueConverterEnum.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/JsonValueConverterEnum.cs index 6e27812..be23946 100644 --- a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/JsonValueConverterEnum.cs +++ b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/JsonValueConverterEnum.cs @@ -2,6 +2,8 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. +using System.Collections.Concurrent; +using System.Globalization; using System.Runtime.CompilerServices; namespace System.Text.Json.Serialization.Converters @@ -9,161 +11,266 @@ namespace System.Text.Json.Serialization.Converters internal class JsonConverterEnum : JsonConverter where T : struct, Enum { - private static readonly TypeCode s_enumTypeCode = Type.GetTypeCode(Enum.GetUnderlyingType(typeof(T))); + private static readonly TypeCode s_enumTypeCode = Type.GetTypeCode(typeof(T)); - public bool TreatAsString { get; private set; } + // Odd type codes are conveniently signed types (for enum backing types). + private static readonly string s_negativeSign = ((int)s_enumTypeCode % 2) == 0 ? null : NumberFormatInfo.CurrentInfo.NegativeSign; + + private readonly EnumConverterOptions _converterOptions; + private readonly JsonNamingPolicy _namingPolicy; + private readonly ConcurrentDictionary _nameCache; public override bool CanConvert(Type type) { return type.IsEnum; } - public JsonConverterEnum(bool treatAsString) + public JsonConverterEnum(EnumConverterOptions options) + : this (options, namingPolicy: null) { - TreatAsString = treatAsString; + } + + public JsonConverterEnum(EnumConverterOptions options, JsonNamingPolicy namingPolicy) + { + _converterOptions = options; + if (namingPolicy != null) + { + _nameCache = new ConcurrentDictionary(); + } + else + { + namingPolicy = JsonNamingPolicy.Default; + } + _namingPolicy = namingPolicy; } public override T Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) { - if (TreatAsString) + JsonTokenType token = reader.TokenType; + + if (token == JsonTokenType.String) { - // Assume the token is a string - if (reader.TokenType != JsonTokenType.String) + if (!_converterOptions.HasFlag(EnumConverterOptions.AllowStrings)) { ThrowHelper.ThrowJsonException(); return default; } + // Try parsing case sensitive first string enumString = reader.GetString(); - if (!Enum.TryParse(enumString, out T value)) + if (!Enum.TryParse(enumString, out T value) + && !Enum.TryParse(enumString, ignoreCase: true, out value)) { ThrowHelper.ThrowJsonException(); return default; } + return value; } - if (reader.TokenType != JsonTokenType.Number) + if (token != JsonTokenType.Number || !_converterOptions.HasFlag(EnumConverterOptions.AllowNumbers)) { ThrowHelper.ThrowJsonException(); return default; } - // When utf8reader/writer will support all primitive types we should remove custom bound checks - // https://github.com/dotnet/corefx/issues/36125 - switch (s_enumTypeCode) { - case TypeCode.SByte: + // Switch cases ordered by expected frequency + + case TypeCode.Int32: + if (reader.TryGetInt32(out int int32)) { - if (reader.TryGetInt32(out int byte8) && JsonHelpers.IsInRangeInclusive(byte8, sbyte.MinValue, sbyte.MaxValue)) - { - sbyte byte8Value = (sbyte)byte8; - return Unsafe.As(ref byte8Value); - } - break; + return Unsafe.As(ref int32); } - case TypeCode.Byte: + break; + case TypeCode.UInt32: + if (reader.TryGetUInt32(out uint uint32)) { - if (reader.TryGetUInt32(out uint ubyte8) && JsonHelpers.IsInRangeInclusive(ubyte8, byte.MinValue, byte.MaxValue)) - { - byte ubyte8Value = (byte)ubyte8; - return Unsafe.As(ref ubyte8Value); - } - break; + return Unsafe.As(ref uint32); } - case TypeCode.Int16: + break; + case TypeCode.UInt64: + if (reader.TryGetUInt64(out ulong uint64)) { - if (reader.TryGetInt32(out int int16) && JsonHelpers.IsInRangeInclusive(int16, short.MinValue, short.MaxValue)) - { - short shortValue = (short)int16; - return Unsafe.As(ref shortValue); - } - break; + return Unsafe.As(ref uint64); } - case TypeCode.UInt16: + break; + case TypeCode.Int64: + if (reader.TryGetInt64(out long int64)) { - if (reader.TryGetUInt32(out uint uint16) && JsonHelpers.IsInRangeInclusive(uint16, ushort.MinValue, ushort.MaxValue)) - { - ushort ushortValue = (ushort)uint16; - return Unsafe.As(ref ushortValue); - } - break; + return Unsafe.As(ref int64); } - case TypeCode.Int32: + break; + + // When utf8reader/writer will support all primitive types we should remove custom bound checks + // https://github.com/dotnet/corefx/issues/36125 + case TypeCode.SByte: + if (reader.TryGetInt32(out int byte8) && JsonHelpers.IsInRangeInclusive(byte8, sbyte.MinValue, sbyte.MaxValue)) { - if (reader.TryGetInt32(out int int32)) - { - return Unsafe.As(ref int32); - } - break; + sbyte byte8Value = (sbyte)byte8; + return Unsafe.As(ref byte8Value); } - case TypeCode.UInt32: + break; + case TypeCode.Byte: + if (reader.TryGetUInt32(out uint ubyte8) && JsonHelpers.IsInRangeInclusive(ubyte8, byte.MinValue, byte.MaxValue)) { - if (reader.TryGetUInt32(out uint uint32)) - { - return Unsafe.As(ref uint32); - } - break; + byte ubyte8Value = (byte)ubyte8; + return Unsafe.As(ref ubyte8Value); } - case TypeCode.Int64: + break; + case TypeCode.Int16: + if (reader.TryGetInt32(out int int16) && JsonHelpers.IsInRangeInclusive(int16, short.MinValue, short.MaxValue)) { - if (reader.TryGetInt64(out long int64)) - { - return Unsafe.As(ref int64); - } - break; + short shortValue = (short)int16; + return Unsafe.As(ref shortValue); } - case TypeCode.UInt64: + break; + case TypeCode.UInt16: + if (reader.TryGetUInt32(out uint uint16) && JsonHelpers.IsInRangeInclusive(uint16, ushort.MinValue, ushort.MaxValue)) { - if (reader.TryGetUInt64(out ulong uint64)) - { - return Unsafe.As(ref uint64); - } - break; + ushort ushortValue = (ushort)uint16; + return Unsafe.As(ref ushortValue); } + break; } ThrowHelper.ThrowJsonException(); return default; } + private static bool IsValidIdentifier(string value) + { + // Trying to do this check efficiently. When an enum is converted to + // string the underlying value is given if it can't find a matching + // identifier (or identifiers in the case of flags). + // + // The underlying value will be given back with a digit (e.g. 0-9) possibly + // preceded by a negative sign. Identifiers have to start with a letter + // so we'll just pick the first valid one and check for a negative sign + // if needed. + return (value[0] >= 'A' && + (s_negativeSign == null || !value.StartsWith(s_negativeSign))); + } + public override void Write(Utf8JsonWriter writer, T value, JsonSerializerOptions options) { - if (TreatAsString) + // If strings are allowed, attempt to write it out as a string value + if (_converterOptions.HasFlag(EnumConverterOptions.AllowStrings)) { - writer.WriteStringValue(value.ToString()); + string original = value.ToString(); + if (_nameCache != null && _nameCache.TryGetValue(original, out string transformed)) + { + writer.WriteStringValue(transformed); + return; + } + + if (IsValidIdentifier(original)) + { + transformed = _namingPolicy.ConvertName(original); + writer.WriteStringValue(transformed); + if (_nameCache != null) + { + _nameCache.TryAdd(original, transformed); + } + return; + } } - else if (s_enumTypeCode == TypeCode.UInt64) + + if (!_converterOptions.HasFlag(EnumConverterOptions.AllowNumbers)) { - // Use the ulong converter to prevent conversion into a signed\long value. - ulong ulongValue = Convert.ToUInt64(value); - writer.WriteNumberValue(ulongValue); + ThrowHelper.ThrowJsonException(); } - else + + switch (s_enumTypeCode) { - // long can hold the signed\unsigned values of other integer types - long longValue = Convert.ToInt64(value); - writer.WriteNumberValue(longValue); + case TypeCode.Int32: + writer.WriteNumberValue(Unsafe.As(ref value)); + break; + case TypeCode.UInt32: + writer.WriteNumberValue(Unsafe.As(ref value)); + break; + case TypeCode.UInt64: + writer.WriteNumberValue(Unsafe.As(ref value)); + break; + case TypeCode.Int64: + writer.WriteNumberValue(Unsafe.As(ref value)); + break; + case TypeCode.Int16: + writer.WriteNumberValue(Unsafe.As(ref value)); + break; + case TypeCode.UInt16: + writer.WriteNumberValue(Unsafe.As(ref value)); + break; + case TypeCode.Byte: + writer.WriteNumberValue(Unsafe.As(ref value)); + break; + case TypeCode.SByte: + writer.WriteNumberValue(Unsafe.As(ref value)); + break; + default: + ThrowHelper.ThrowJsonException(); + break; } } public override void Write(Utf8JsonWriter writer, T value, JsonEncodedText propertyName, JsonSerializerOptions options) { - if (TreatAsString) + // If strings are allowed, attempt to write it out as a string value + if (_converterOptions.HasFlag(EnumConverterOptions.AllowStrings)) { - writer.WriteString(propertyName, value.ToString()); + string original = value.ToString(); + if (_nameCache != null && _nameCache.TryGetValue(original, out string transformed)) + { + writer.WriteString(propertyName, transformed); + return; + } + + if (IsValidIdentifier(original)) + { + transformed = _namingPolicy.ConvertName(original); + writer.WriteString(propertyName, transformed); + if (_nameCache != null) + { + _nameCache.TryAdd(original, transformed); + } + return; + } } - else if (s_enumTypeCode == TypeCode.UInt64) + + if (!_converterOptions.HasFlag(EnumConverterOptions.AllowNumbers)) { - // Use the ulong converter to prevent conversion into a signed\long value. - ulong ulongValue = Convert.ToUInt64(value); - writer.WriteNumber(propertyName, ulongValue); + ThrowHelper.ThrowJsonException(); } - else + + switch (s_enumTypeCode) { - // long can hold the signed\unsigned values of other integer types. - long longValue = Convert.ToInt64(value); - writer.WriteNumber(propertyName, longValue); + case TypeCode.Int32: + writer.WriteNumber(propertyName, Unsafe.As(ref value)); + break; + case TypeCode.UInt32: + writer.WriteNumber(propertyName, Unsafe.As(ref value)); + break; + case TypeCode.UInt64: + writer.WriteNumber(propertyName, Unsafe.As(ref value)); + break; + case TypeCode.Int64: + writer.WriteNumber(propertyName, Unsafe.As(ref value)); + break; + case TypeCode.Int16: + writer.WriteNumber(propertyName, Unsafe.As(ref value)); + break; + case TypeCode.UInt16: + writer.WriteNumber(propertyName, Unsafe.As(ref value)); + break; + case TypeCode.Byte: + writer.WriteNumber(propertyName, Unsafe.As(ref value)); + break; + case TypeCode.SByte: + writer.WriteNumber(propertyName, Unsafe.As(ref value)); + break; + default: + ThrowHelper.ThrowJsonException(); + break; } } } diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonDefaultNamingPolicy.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonDefaultNamingPolicy.cs new file mode 100644 index 0000000..0f56d40 --- /dev/null +++ b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonDefaultNamingPolicy.cs @@ -0,0 +1,11 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +namespace System.Text.Json +{ + internal class JsonDefaultNamingPolicy : JsonNamingPolicy + { + public override string ConvertName(string name) => name; + } +} diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonNamingPolicy.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonNamingPolicy.cs index 1c8eeda..387d7ba 100644 --- a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonNamingPolicy.cs +++ b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonNamingPolicy.cs @@ -19,6 +19,8 @@ namespace System.Text.Json /// public static JsonNamingPolicy CamelCase { get; } = new JsonCamelCaseNamePolicy(); + internal static JsonNamingPolicy Default { get; } = new JsonDefaultNamingPolicy(); + /// /// When overridden in a derived class, converts the specified name according to the policy. /// diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializerOptions.Converters.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializerOptions.Converters.cs index bb14f5f..4776b11 100644 --- a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializerOptions.Converters.cs +++ b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializerOptions.Converters.cs @@ -47,7 +47,7 @@ namespace System.Text.Json var converters = new List(NumberOfConverters); // Use a list for converters that implement CanConvert(). - converters.Add(new JsonConverterEnum(treatAsString: false)); + converters.Add(new JsonConverterEnum()); converters.Add(new JsonKeyValuePairConverter()); // We will likely add collection converters here in the future. diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonStringEnumConverter.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonStringEnumConverter.cs new file mode 100644 index 0000000..f59195d --- /dev/null +++ b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonStringEnumConverter.cs @@ -0,0 +1,58 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System.Reflection; +using System.Text.Json.Serialization.Converters; + +namespace System.Text.Json.Serialization +{ + /// + /// Converter to convert enums to and from strings. + /// + /// + /// Reading is case insensitive, writing can be customized via a . + /// + public sealed class JsonStringEnumConverter : JsonConverterFactory + { + private readonly JsonNamingPolicy _namingPolicy; + private readonly EnumConverterOptions _converterOptions; + + /// + /// Constructor. + /// + /// + /// Optional naming policy for writing enum values. + /// + /// + /// True to allow undefined enum values. When true, if an enum value isn't + /// defined it will output as a number rather than a string. + /// + public JsonStringEnumConverter(JsonNamingPolicy namingPolicy = null, bool allowIntegerValues = true) + { + _namingPolicy = namingPolicy; + _converterOptions = allowIntegerValues + ? EnumConverterOptions.AllowNumbers | EnumConverterOptions.AllowStrings + : EnumConverterOptions.AllowStrings; + } + + /// + public override bool CanConvert(Type type) + { + return type.IsEnum; + } + + /// + protected override JsonConverter CreateConverter(Type type) + { + JsonConverter converter = (JsonConverter)Activator.CreateInstance( + typeof(JsonConverterEnum<>).MakeGenericType(type), + BindingFlags.Instance | BindingFlags.Public, + binder: null, + new object[] { _converterOptions, _namingPolicy }, + culture: null); + + return converter; + } + } +} diff --git a/src/libraries/System.Text.Json/tests/Serialization/EnumConverterTests.cs b/src/libraries/System.Text.Json/tests/Serialization/EnumConverterTests.cs new file mode 100644 index 0000000..e6171c6 --- /dev/null +++ b/src/libraries/System.Text.Json/tests/Serialization/EnumConverterTests.cs @@ -0,0 +1,117 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System.IO; +using Xunit; + +namespace System.Text.Json.Serialization.Tests +{ + public class EnumConverterTests + { + [Fact] + public void ConvertDayOfWeek() + { + JsonSerializerOptions options = new JsonSerializerOptions(); + options.Converters.Add(new JsonStringEnumConverter()); + + WhenClass when = JsonSerializer.Parse(@"{""Day"":""Monday""}", options); + Assert.Equal(DayOfWeek.Monday, when.Day); + DayOfWeek day = JsonSerializer.Parse(@"""Tuesday""", options); + Assert.Equal(DayOfWeek.Tuesday, day); + + // We are case insensitive on read + day = JsonSerializer.Parse(@"""wednesday""", options); + Assert.Equal(DayOfWeek.Wednesday, day); + + // Numbers work by default + day = JsonSerializer.Parse(@"4", options); + Assert.Equal(DayOfWeek.Thursday, day); + + string json = JsonSerializer.ToString(DayOfWeek.Friday, options); + Assert.Equal(@"""Friday""", json); + + // Try a unique naming policy + options = new JsonSerializerOptions(); + options.Converters.Add(new JsonStringEnumConverter(new ToLower())); + + json = JsonSerializer.ToString(DayOfWeek.Friday, options); + Assert.Equal(@"""friday""", json); + + // Undefined values should come out as a number (not a string) + json = JsonSerializer.ToString((DayOfWeek)(-1), options); + Assert.Equal(@"-1", json); + + // Not permitting integers should throw + options = new JsonSerializerOptions(); + options.Converters.Add(new JsonStringEnumConverter(allowIntegerValues: false)); + Assert.Throws(() => JsonSerializer.ToString((DayOfWeek)(-1), options)); + } + + public class ToLower : JsonNamingPolicy + { + public override string ConvertName(string name) => name.ToLowerInvariant(); + } + + public class WhenClass + { + public DayOfWeek Day { get; set; } + } + + [Fact] + public void ConvertFileAttributes() + { + JsonSerializerOptions options = new JsonSerializerOptions(); + options.Converters.Add(new JsonStringEnumConverter()); + + FileState state = JsonSerializer.Parse(@"{""Attributes"":""ReadOnly""}", options); + Assert.Equal(FileAttributes.ReadOnly, state.Attributes); + state = JsonSerializer.Parse(@"{""Attributes"":""Directory, ReparsePoint""}", options); + Assert.Equal(FileAttributes.Directory | FileAttributes.ReparsePoint, state.Attributes); + FileAttributes attributes = JsonSerializer.Parse(@"""Normal""", options); + Assert.Equal(FileAttributes.Normal, attributes); + attributes = JsonSerializer.Parse(@"""System, SparseFile""", options); + Assert.Equal(FileAttributes.System | FileAttributes.SparseFile, attributes); + + // We are case insensitive on read + attributes = JsonSerializer.Parse(@"""OFFLINE""", options); + Assert.Equal(FileAttributes.Offline, attributes); + attributes = JsonSerializer.Parse(@"""compressed, notcontentindexed""", options); + Assert.Equal(FileAttributes.Compressed | FileAttributes.NotContentIndexed, attributes); + + // Numbers are cool by default + attributes = JsonSerializer.Parse(@"131072", options); + Assert.Equal(FileAttributes.NoScrubData, attributes); + attributes = JsonSerializer.Parse(@"3", options); + Assert.Equal(FileAttributes.Hidden | FileAttributes.ReadOnly, attributes); + + string json = JsonSerializer.ToString(FileAttributes.Hidden, options); + Assert.Equal(@"""Hidden""", json); + json = JsonSerializer.ToString(FileAttributes.Temporary | FileAttributes.Offline, options); + Assert.Equal(@"""Temporary, Offline""", json); + + // Try a unique casing + options = new JsonSerializerOptions(); + options.Converters.Add(new JsonStringEnumConverter(new ToLower())); + + json = JsonSerializer.ToString(FileAttributes.NoScrubData, options); + Assert.Equal(@"""noscrubdata""", json); + json = JsonSerializer.ToString(FileAttributes.System | FileAttributes.Offline, options); + Assert.Equal(@"""system, offline""", json); + + // Undefined values should come out as a number (not a string) + json = JsonSerializer.ToString((FileAttributes)(-1), options); + Assert.Equal(@"-1", json); + + // Not permitting integers should throw + options = new JsonSerializerOptions(); + options.Converters.Add(new JsonStringEnumConverter(allowIntegerValues: false)); + Assert.Throws(() => JsonSerializer.ToString((FileAttributes)(-1), options)); + } + + public class FileState + { + public FileAttributes Attributes { get; set; } + } + } +} diff --git a/src/libraries/System.Text.Json/tests/System.Text.Json.Tests.csproj b/src/libraries/System.Text.Json/tests/System.Text.Json.Tests.csproj index d624a00..8d9d141 100644 --- a/src/libraries/System.Text.Json/tests/System.Text.Json.Tests.csproj +++ b/src/libraries/System.Text.Json/tests/System.Text.Json.Tests.csproj @@ -50,6 +50,7 @@ +