From bbb30f0ae000da2fd85bd7839486374e02f82067 Mon Sep 17 00:00:00 2001 From: Konstantin Sharon Date: Thu, 11 Mar 2021 16:13:09 +0200 Subject: [PATCH] Use JsonPropertyName in JsonStringEnumMemberConverter. Fixes #16. --- .../JsonMicrosoftDateTimeOffsetConverter.cs | 2 +- .../JsonStringEnumMemberConverterHelper.cs | 7 +- .../Macross.Json.Extensions/README.md | 27 ++ ...numMemberConverterJsonPropertyNameTests.cs | 232 ++++++++++++++++++ 4 files changed, 266 insertions(+), 2 deletions(-) create mode 100644 ClassLibraries/Macross.Json.Extensions/Test/JsonStringEnumMemberConverterJsonPropertyNameTests.cs diff --git a/ClassLibraries/Macross.Json.Extensions/Code/System.Text.Json.Serialization/JsonMicrosoftDateTimeOffsetConverter.cs b/ClassLibraries/Macross.Json.Extensions/Code/System.Text.Json.Serialization/JsonMicrosoftDateTimeOffsetConverter.cs index b299184..bb86607 100644 --- a/ClassLibraries/Macross.Json.Extensions/Code/System.Text.Json.Serialization/JsonMicrosoftDateTimeOffsetConverter.cs +++ b/ClassLibraries/Macross.Json.Extensions/Code/System.Text.Json.Serialization/JsonMicrosoftDateTimeOffsetConverter.cs @@ -109,7 +109,7 @@ public static void WriteDateTimeOffset(Utf8JsonWriter writer, DateTimeOffset val } JsonMicrosoftDateTimeConverter.Start.CopyTo(span); - span[7 + bytesWritten] = utcOffset >= TimeSpan.Zero ? 0x2B : 0x2D; + span[7 + bytesWritten] = utcOffset >= TimeSpan.Zero ? (byte)0x2B : (byte)0x2D; int hours = Math.Abs(utcOffset.Hours); if (hours < 10) diff --git a/ClassLibraries/Macross.Json.Extensions/Code/System.Text.Json.Serialization/JsonStringEnumMemberConverterHelper.cs b/ClassLibraries/Macross.Json.Extensions/Code/System.Text.Json.Serialization/JsonStringEnumMemberConverterHelper.cs index f77c84a..de935a5 100644 --- a/ClassLibraries/Macross.Json.Extensions/Code/System.Text.Json.Serialization/JsonStringEnumMemberConverterHelper.cs +++ b/ClassLibraries/Macross.Json.Extensions/Code/System.Text.Json.Serialization/JsonStringEnumMemberConverterHelper.cs @@ -62,7 +62,12 @@ public JsonStringEnumMemberConverterHelper(JsonNamingPolicy? namingPolicy, bool string name = builtInNames[i]; FieldInfo field = _EnumType.GetField(name, EnumBindings)!; EnumMemberAttribute? enumMemberAttribute = field.GetCustomAttribute(true); - string transformedName = enumMemberAttribute?.Value ?? namingPolicy?.ConvertName(name) ?? name; + JsonPropertyNameAttribute? jsonPropertyNameAttribute = field.GetCustomAttribute(true); + + string transformedName = enumMemberAttribute?.Value ?? + jsonPropertyNameAttribute?.Name ?? + namingPolicy?.ConvertName(name) ?? + name; if (enumValue is not TEnum typedValue) throw new NotSupportedException(); diff --git a/ClassLibraries/Macross.Json.Extensions/README.md b/ClassLibraries/Macross.Json.Extensions/README.md index a66febe..c17f66b 100644 --- a/ClassLibraries/Macross.Json.Extensions/README.md +++ b/ClassLibraries/Macross.Json.Extensions/README.md @@ -47,6 +47,33 @@ but it adds two features and fixes one bug. } ``` +* [JsonPropertyName](https://docs.microsoft.com/en-us/dotnet/api/system.text.json.serialization.jsonpropertynameattribute) + Support + + When serializing and deserializing an Enum as a string the value specified + by `EnumMember` will be used. + + ```csharp + [JsonConverter(typeof(JsonStringEnumMemberConverter))] + public enum DefinitionType + { + [JsonPropertyName("UNKNOWN_DEFINITION_000")] + DefinitionUnknown + } + + [TestMethod] + public void ExampleTest() + { + string Json = JsonSerializer.Serialize(DefinitionType.DefinitionUnknown); + + Assert.AreEqual("\"UNKNOWN_DEFINITION_000\"", Json); + + DefinitionType ParsedDefinitionType = JsonSerializer.Deserialize(Json); + + Assert.AreEqual(DefinitionType.DefinitionUnknown, ParsedDefinitionType); + } + ``` + * Nullable<Enum> Support If you try to use the built-in `JsonStringEnumConverter` with a nullable diff --git a/ClassLibraries/Macross.Json.Extensions/Test/JsonStringEnumMemberConverterJsonPropertyNameTests.cs b/ClassLibraries/Macross.Json.Extensions/Test/JsonStringEnumMemberConverterJsonPropertyNameTests.cs new file mode 100644 index 0000000..eb18784 --- /dev/null +++ b/ClassLibraries/Macross.Json.Extensions/Test/JsonStringEnumMemberConverterJsonPropertyNameTests.cs @@ -0,0 +1,232 @@ +#if NET5_0 + +using System; +using System.Text.Json; +using System.Text.Json.Serialization; + +using Microsoft.VisualStudio.TestTools.UnitTesting; + +namespace Macross.Json.Extensions.Tests +{ + [TestClass] + public class JsonStringEnumMemberConverterJsonPropertyNameTests + { + [TestMethod] + public void EnumMemberSerializationTest() + { + string Json = JsonSerializer.Serialize(FlagDefinitions.Four); + Assert.AreEqual(@"""four value""", Json); + + Json = JsonSerializer.Serialize(FlagDefinitions.Four | FlagDefinitions.One); + Assert.AreEqual(@"""one value, four value""", Json); + + Json = JsonSerializer.Serialize((FlagDefinitions)255); + Assert.AreEqual("255", Json); + } + + [TestMethod] + public void EnumMemberDeserializationTest() + { + FlagDefinitions Value = JsonSerializer.Deserialize(@"""all values"""); + Assert.AreEqual(FlagDefinitions.All, Value); + + Value = JsonSerializer.Deserialize(@"""two value, three value"""); + Assert.AreEqual(FlagDefinitions.Two | FlagDefinitions.Three, Value); + + Value = JsonSerializer.Deserialize(@"""tWo VALUE"""); + Assert.AreEqual(FlagDefinitions.Two, Value); + } + + [ExpectedException(typeof(JsonException))] + [TestMethod] + public void EnumMemberInvalidTypeDeserializationTest() => JsonSerializer.Deserialize(@"null"); + + [ExpectedException(typeof(JsonException))] + [TestMethod] + public void EnumMemberInvalidValueDeserializationTest() => JsonSerializer.Deserialize(@"""invalid_value"""); + + [ExpectedException(typeof(JsonException))] + [TestMethod] + public void EnumMemberInvalidNumericValueDeserializationTest() + { + JsonSerializerOptions options = new JsonSerializerOptions(); + options.Converters.Add(new JsonStringEnumMemberConverter(allowIntegerValues: false)); + + JsonSerializer.Serialize((FlagDefinitions)255, options); + } + + [TestMethod] + public void NullableEnumSerializationTest() + { + JsonSerializerOptions Options = new JsonSerializerOptions(); + Options.Converters.Add(new JsonStringEnumMemberConverter()); + + string Json = JsonSerializer.Serialize((DayOfWeek?)null, Options); + Assert.AreEqual("null", Json); + + Json = JsonSerializer.Serialize((DayOfWeek?)DayOfWeek.Monday, Options); + Assert.AreEqual(@"""Monday""", Json); + + Json = JsonSerializer.Serialize((EnumDefinition?)255, Options); + Assert.AreEqual("255", Json); + } + + [TestMethod] + public void NullableEnumDeserializationTest() + { + JsonSerializerOptions Options = new JsonSerializerOptions(); + Options.Converters.Add(new JsonStringEnumMemberConverter()); + + DayOfWeek? Value = JsonSerializer.Deserialize("null", Options); + Assert.AreEqual(null, Value); + + Value = JsonSerializer.Deserialize(@"""Friday""", Options); + Assert.AreEqual(DayOfWeek.Friday, Value); + + EnumDefinition? EnumValue = JsonSerializer.Deserialize(@"""fIrSt""", Options); + Assert.AreEqual(EnumDefinition.First, EnumValue); + + EnumValue = JsonSerializer.Deserialize(@"255", Options); + Assert.AreEqual(255, (int)EnumValue!); + } + + [TestMethod] + public void EnumMemberSerializationOptionsTest() + { + JsonSerializerOptions options = new JsonSerializerOptions + { + Converters = { new JsonStringEnumMemberConverter(JsonNamingPolicy.CamelCase) } + }; + + string json = JsonSerializer.Serialize(EnumDefinition.First, options); + Assert.AreEqual(@"""first""", json); + + json = JsonSerializer.Serialize(EnumDefinition.Second, options); + Assert.AreEqual(@"""_second""", json); + } + + [TestMethod] + public void EnumMemberDeserializationOptionsTest() + { + JsonSerializerOptions options = new JsonSerializerOptions + { + Converters = { new JsonStringEnumMemberConverter(JsonNamingPolicy.CamelCase) } + }; + + EnumDefinition Value = JsonSerializer.Deserialize(@"""first""", options); + Assert.AreEqual(EnumDefinition.First, Value); + + Value = JsonSerializer.Deserialize(@"""_second""", options); + Assert.AreEqual(EnumDefinition.Second, Value); + } + + [ExpectedException(typeof(JsonException))] + [TestMethod] + public void EnumMemberInvalidDeserializationOptionsTest() + { + JsonSerializerOptions options = new JsonSerializerOptions + { + Converters = { new JsonStringEnumMemberConverter() } + }; + + JsonSerializer.Deserialize(@"""invalid_value""", options); + } + + [ExpectedException(typeof(JsonException))] + [TestMethod] + public void EnumMemberInvalidTypeDeserializationOptionsTest() + { + JsonSerializerOptions options = new JsonSerializerOptions + { + Converters = { new JsonStringEnumMemberConverter(allowIntegerValues: false) } + }; + + JsonSerializer.Deserialize(@"255", options); + } + + [TestMethod] + public void EnumMemberInvalidDeserializationIncludesJsonPathInMessageTest() + { + JsonSerializerOptions options = new JsonSerializerOptions + { + Converters = { new JsonStringEnumMemberConverter() } + }; + + try + { + JsonSerializer.Deserialize(@"""invalid_value""", options); + Assert.Fail($"A {nameof(JsonException)} is expected to be thrown."); + } +#pragma warning disable CA1031 // Do not catch general exception types + catch (Exception exception) +#pragma warning restore CA1031 // Do not catch general exception types + { + if (exception is JsonException jsonException) + { + StringAssert.Contains(jsonException.Message, ". Path: $"); + } + else + { + Assert.Fail($"A {nameof(JsonException)} is expected to be thrown but a {exception.GetType().FullName} was thrown."); + } + } + } + + [TestMethod] + public void EnumMemberFlagInvalidDeserializationIncludesJsonPathInMessageTest() + { + JsonSerializerOptions options = new JsonSerializerOptions + { + Converters = { new JsonStringEnumMemberConverter() } + }; + + try + { + JsonSerializer.Deserialize(@"""invalid_value""", options); + Assert.Fail($"A {nameof(JsonException)} is expected to be thrown."); + } +#pragma warning disable CA1031 // Do not catch general exception types + catch (Exception exception) +#pragma warning restore CA1031 // Do not catch general exception types + { + if (exception is JsonException jsonException) + { + StringAssert.Contains(jsonException.Message, ". Path: $"); + } + else + { + Assert.Fail($"A {nameof(JsonException)} is expected to be thrown but a {exception.GetType().FullName} was thrown."); + } + } + } + + [JsonConverter(typeof(JsonStringEnumMemberConverter))] + [Flags] + public enum FlagDefinitions + { + None = 0x00, + + [JsonPropertyName("all values")] + All = One | Two | Three | Four, + + [JsonPropertyName("one value")] + One = 0x01, + [JsonPropertyName("two value")] + Two = 0x02, + [JsonPropertyName("three value")] + Three = 0x04, + [JsonPropertyName("four value")] + Four = 0x08, + } + + public enum EnumDefinition + { + First, + + [JsonPropertyName("_second")] + Second, + } + } +} + +#endif \ No newline at end of file