From 3f70a14aec6f0881ef90f5a24634fb6a8b81a1ae Mon Sep 17 00:00:00 2001 From: Jan Trejbal Date: Mon, 23 Oct 2023 20:12:25 +0200 Subject: [PATCH] Support enum as property name (#27) * Add SerializeEnumDictionaryWorks * Implement ReadAsPropertyName, WriteAsPropertyName * Add IReadOnlyDictionary into MyJsonSerializerContext * Fix ReadAsPropertyName * Fix TryToEnum * Add DeserializeEnumDictionaryWorks * Update fixture * Apply ReplaceLineEndings --- .../EnumSerializationTests.cs | 59 +++++++++ .../MyJsonSerializerContext.cs | 3 + ...EmptyWorks#EnumJsonConvertor.g.verified.cs | 113 +++++++++++++++++- ...BackingEnumEnumJsonConverter.g.verified.cs | 23 ++-- ...ropertyEnumEnumJsonConverter.g.verified.cs | 19 +-- ...BackingEnumEnumJsonConverter.g.verified.cs | 34 +++--- ...ationWorks#EnumJsonConvertor.g.verified.cs | 113 +++++++++++++++++- ...ype#EMyEnumEnumJsonConverter.g.verified.cs | 37 +++--- ...ackingType#EnumJsonConvertor.g.verified.cs | 113 +++++++++++++++++- ...ame#EMyEnumEnumJsonConverter.g.verified.cs | 37 +++--- ...stEnumName#EnumJsonConvertor.g.verified.cs | 113 +++++++++++++++++- ...rks#EMyEnumEnumJsonConverter.g.verified.cs | 19 +-- ...emberWorks#EnumJsonConvertor.g.verified.cs | 113 +++++++++++++++++- ...rtingWorks#EnumJsonConvertor.g.verified.cs | 113 +++++++++++++++++- ...ype#EMyEnumEnumJsonConverter.g.verified.cs | 25 ++-- ...ackingType#EnumJsonConvertor.g.verified.cs | 113 +++++++++++++++++- ...ame#EMyEnumEnumJsonConverter.g.verified.cs | 36 +++--- ...seEnumName#EnumJsonConvertor.g.verified.cs | 113 +++++++++++++++++- ...ame#EMyEnumEnumJsonConverter.g.verified.cs | 21 ++-- ...seEnumName#EnumJsonConvertor.g.verified.cs | 113 +++++++++++++++++- ...rks#EMyEnumEnumJsonConverter.g.verified.cs | 19 +-- ...impleWorks#EnumJsonConvertor.g.verified.cs | 113 +++++++++++++++++- .../EnumJsonConvertor.cs | 113 +++++++++++++++++- .../Generators/EnumJsonConverterGenerator.cs | 63 ++++++---- 24 files changed, 1457 insertions(+), 181 deletions(-) diff --git a/src/Aviationexam.GeneratedJsonConverters.SourceGenerator.Target.Tests/EnumSerializationTests.cs b/src/Aviationexam.GeneratedJsonConverters.SourceGenerator.Target.Tests/EnumSerializationTests.cs index 76238aa..51cb74b 100644 --- a/src/Aviationexam.GeneratedJsonConverters.SourceGenerator.Target.Tests/EnumSerializationTests.cs +++ b/src/Aviationexam.GeneratedJsonConverters.SourceGenerator.Target.Tests/EnumSerializationTests.cs @@ -1,5 +1,6 @@ using Aviationexam.GeneratedJsonConverters.SourceGenerator.Target.Contracts; using System; +using System.Collections.Generic; using System.Text.Json; using Xunit; @@ -80,4 +81,62 @@ string sourceJson Assert.Equal(expectedValue, serializedValue); } + + [Theory] + [MemberData(nameof(EnumDictionaryData))] + public void SerializeEnumDictionaryWorks(Type type, object dictionary, string json) + { + var serializedValue = JsonSerializer.Serialize( + dictionary, + type, + MyJsonSerializerContext.Default.Options + ); + + Assert.Equal(json, serializedValue.ReplaceLineEndings("\n")); + } + + [Theory] + [MemberData(nameof(EnumDictionaryData))] + public void DeserializeEnumDictionaryWorks(Type type, object dictionary, string json) + { + var deserializedDictionary = JsonSerializer.Deserialize( + json, + type, + MyJsonSerializerContext.Default.Options + ); + + Assert.Equal(dictionary, deserializedDictionary); + } + + public static IEnumerable EnumDictionaryData() + { + yield return new object[] + { + typeof(IReadOnlyDictionary), + new Dictionary + { + [EBackingEnum.B] = 2, + }, + // language=json + """ + { + "1": 2 + } + """.ReplaceLineEndings("\n"), + }; + yield return new object[] + { + typeof(IReadOnlyDictionary), + new Dictionary + { + [EConfiguredPropertyEnum.B] = 2, + }, + // language=json + """ + { + "D": 2 + } + """.ReplaceLineEndings("\n"), + }; + } } diff --git a/src/Aviationexam.GeneratedJsonConverters.SourceGenerator.Target/MyJsonSerializerContext.cs b/src/Aviationexam.GeneratedJsonConverters.SourceGenerator.Target/MyJsonSerializerContext.cs index 0b0d1ca..8da01b4 100644 --- a/src/Aviationexam.GeneratedJsonConverters.SourceGenerator.Target/MyJsonSerializerContext.cs +++ b/src/Aviationexam.GeneratedJsonConverters.SourceGenerator.Target/MyJsonSerializerContext.cs @@ -1,5 +1,6 @@ using Aviationexam.GeneratedJsonConverters.SourceGenerator.Target.Contracts; using Aviationexam.GeneratedJsonConverters.SourceGenerator.Target.ContractWithCustomDelimiter; +using System.Collections.Generic; using System.Text.Json.Serialization; namespace Aviationexam.GeneratedJsonConverters.SourceGenerator.Target; @@ -21,6 +22,8 @@ namespace Aviationexam.GeneratedJsonConverters.SourceGenerator.Target; [JsonSerializable(typeof(EMyEnum))] [JsonSerializable(typeof(EPropertyEnum))] [JsonSerializable(typeof(EPropertyWithBackingEnum))] +[JsonSerializable(typeof(IReadOnlyDictionary))] +[JsonSerializable(typeof(IReadOnlyDictionary))] public partial class MyJsonSerializerContext : JsonSerializerContext { static MyJsonSerializerContext() diff --git a/src/Aviationexam.GeneratedJsonConverters.SourceGenerator.Tests/EnumJsonConverterIncrementalGeneratorSnapshotTests.EmptyWorks#EnumJsonConvertor.g.verified.cs b/src/Aviationexam.GeneratedJsonConverters.SourceGenerator.Tests/EnumJsonConverterIncrementalGeneratorSnapshotTests.EmptyWorks#EnumJsonConvertor.g.verified.cs index 1fc6eaa..34d3193 100644 --- a/src/Aviationexam.GeneratedJsonConverters.SourceGenerator.Tests/EnumJsonConverterIncrementalGeneratorSnapshotTests.EmptyWorks#EnumJsonConvertor.g.verified.cs +++ b/src/Aviationexam.GeneratedJsonConverters.SourceGenerator.Tests/EnumJsonConverterIncrementalGeneratorSnapshotTests.EmptyWorks#EnumJsonConvertor.g.verified.cs @@ -22,9 +22,9 @@ internal abstract class EnumJsonConvertor : JsonConverter protected abstract EnumSerializationStrategy SerializationStrategy { get; } - protected abstract T ToEnum(ReadOnlySpan enumName); + protected abstract bool TryToEnum(ReadOnlySpan enumName, out T value); - protected abstract T ToEnum(TBackingType numericValue); + protected abstract bool TryToEnum(TBackingType numericValue, out T value); protected abstract TBackingType ToBackingType(T value); @@ -41,7 +41,14 @@ reader.TokenType is JsonTokenType.String { var enumName = reader.ValueSpan; - return ToEnum(enumName); + if (TryToEnum(enumName, out var enumValue)) + { + return enumValue; + } + + var stringValue = Encoding.UTF8.GetString(enumName.ToArray()); + + throw new JsonException($"Undefined mapping of '{stringValue}' to enum '{typeof(T).FullName}'"); } if (reader.TokenType is JsonTokenType.Number) @@ -50,7 +57,12 @@ reader.TokenType is JsonTokenType.String if (numericValue.HasValue) { - return ToEnum(numericValue.Value); + if (TryToEnum(numericValue.Value, out var enumValue)) + { + return enumValue; + } + + throw new JsonException($"Undefined mapping of '{numericValue}' to enum '{{enumFullName}}'"); } } @@ -59,6 +71,44 @@ reader.TokenType is JsonTokenType.String throw new JsonException($"Unable to deserialize {value}('{reader.TokenType}') into {typeof(T).Name}"); } + public override T ReadAsPropertyName( + ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options + ) + { + if ( + reader.TokenType is JsonTokenType.PropertyName + && DeserializationStrategy.HasFlag(EnumDeserializationStrategy.UseEnumName) + ) + { + var enumName = reader.ValueSpan; + + if (TryToEnum(enumName, out var enumValue)) + { + return enumValue; + } + } + + var value = Encoding.UTF8.GetString(reader.ValueSpan.ToArray()); + + if ( + reader.TokenType is JsonTokenType.PropertyName + && DeserializationStrategy.HasFlag(EnumDeserializationStrategy.UseBackingType) + ) + { + var numericValue = ParseAsNumber(value); + + if (numericValue.HasValue) + { + if (TryToEnum(numericValue.Value, out var enumValue)) + { + return enumValue; + } + } + } + + throw new JsonException($"Unable to deserialize {value}('{reader.TokenType}') into {typeof(T).Name}"); + } + private TBackingType? ReadAsNumber(ref Utf8JsonReader reader) => BackingTypeTypeCode switch { TypeCode.SByte => reader.GetSByte() is var numericValue ? Unsafe.As(ref numericValue) : null, @@ -72,6 +122,21 @@ reader.TokenType is JsonTokenType.String _ => throw new ArgumentOutOfRangeException(nameof(BackingTypeTypeCode), BackingTypeTypeCode, $"Unexpected TypeCode {BackingTypeTypeCode}") }; + private TBackingType? ParseAsNumber( + string value + ) => BackingTypeTypeCode switch + { + TypeCode.SByte => sbyte.TryParse(value, out var numericValue) ? Unsafe.As(ref numericValue) : null, + TypeCode.Byte => byte.TryParse(value, out var numericValue) ? Unsafe.As(ref numericValue) : null, + TypeCode.Int16 => short.TryParse(value, out var numericValue) ? Unsafe.As(ref numericValue) : null, + TypeCode.UInt16 => ushort.TryParse(value, out var numericValue) ? Unsafe.As(ref numericValue) : null, + TypeCode.Int32 => int.TryParse(value, out var numericValue) ? Unsafe.As(ref numericValue) : null, + TypeCode.UInt32 => uint.TryParse(value, out var numericValue) ? Unsafe.As(ref numericValue) : null, + TypeCode.Int64 => long.TryParse(value, out var numericValue) ? Unsafe.As(ref numericValue) : null, + TypeCode.UInt64 => ulong.TryParse(value, out var numericValue) ? Unsafe.As(ref numericValue) : null, + _ => throw new ArgumentOutOfRangeException(nameof(BackingTypeTypeCode), BackingTypeTypeCode, $"Unexpected TypeCode {BackingTypeTypeCode}") + }; + public override void Write(Utf8JsonWriter writer, T value, JsonSerializerOptions options) { if (SerializationStrategy is EnumSerializationStrategy.BackingType) @@ -88,6 +153,22 @@ public override void Write(Utf8JsonWriter writer, T value, JsonSerializerOptions } } + public override void WriteAsPropertyName(Utf8JsonWriter writer, T value, JsonSerializerOptions options) + { + if (SerializationStrategy is EnumSerializationStrategy.BackingType) + { + WriteAsPropertyNameAsBackingType(writer, value, options); + } + else if (SerializationStrategy is EnumSerializationStrategy.FirstEnumName) + { + WriteAsPropertyNameAsFirstEnumName(writer, value, options); + } + else + { + throw new ArgumentOutOfRangeException(nameof(SerializationStrategy), SerializationStrategy, "Unknown serialization strategy"); + } + } + private void WriteAsBackingType( Utf8JsonWriter writer, T value, @@ -128,6 +209,18 @@ JsonSerializerOptions options } } + private void WriteAsPropertyNameAsBackingType( + Utf8JsonWriter writer, + T value, + [SuppressMessage("ReSharper", "UnusedParameter.Local")] + JsonSerializerOptions options + ) + { + var numericValue = ToBackingType(value); + + writer.WritePropertyName($"{numericValue}"); + } + private void WriteAsFirstEnumName( Utf8JsonWriter writer, T value, @@ -139,4 +232,16 @@ JsonSerializerOptions options writer.WriteStringValue(enumValue); } + + private void WriteAsPropertyNameAsFirstEnumName( + Utf8JsonWriter writer, + T value, + [SuppressMessage("ReSharper", "UnusedParameter.Local")] + JsonSerializerOptions options + ) + { + var enumValue = ToFirstEnumName(value); + + writer.WritePropertyName(enumValue); + } } diff --git a/src/Aviationexam.GeneratedJsonConverters.SourceGenerator.Tests/EnumJsonConverterIncrementalGeneratorSnapshotTests.EnumWithConfigurationWorks#EBackingEnumEnumJsonConverter.g.verified.cs b/src/Aviationexam.GeneratedJsonConverters.SourceGenerator.Tests/EnumJsonConverterIncrementalGeneratorSnapshotTests.EnumWithConfigurationWorks#EBackingEnumEnumJsonConverter.g.verified.cs index f2ff658..5b78db6 100644 --- a/src/Aviationexam.GeneratedJsonConverters.SourceGenerator.Tests/EnumJsonConverterIncrementalGeneratorSnapshotTests.EnumWithConfigurationWorks#EBackingEnumEnumJsonConverter.g.verified.cs +++ b/src/Aviationexam.GeneratedJsonConverters.SourceGenerator.Tests/EnumJsonConverterIncrementalGeneratorSnapshotTests.EnumWithConfigurationWorks#EBackingEnumEnumJsonConverter.g.verified.cs @@ -11,18 +11,23 @@ internal class EBackingEnumEnumJsonConverter : Aviationexam.GeneratedJsonConvert protected override Aviationexam.GeneratedJsonConverters.EnumSerializationStrategy SerializationStrategy => Aviationexam.GeneratedJsonConverters.EnumSerializationStrategy.BackingType; - protected override ApplicationNamespace.Contracts.EBackingEnum ToEnum( - System.ReadOnlySpan enumName + protected override bool TryToEnum( + System.ReadOnlySpan enumName, out ApplicationNamespace.Contracts.EBackingEnum value ) => throw new System.Text.Json.JsonException("Enum is not configured to support deserialization from enum name"); - protected override ApplicationNamespace.Contracts.EBackingEnum ToEnum( - System.Int32 numericValue - ) => numericValue switch + protected override bool TryToEnum( + System.Int32 numericValue, out ApplicationNamespace.Contracts.EBackingEnum value + ) { - 0 => ApplicationNamespace.Contracts.EBackingEnum.A, - 1 => ApplicationNamespace.Contracts.EBackingEnum.B, - _ => throw new System.Text.Json.JsonException($"Undefined mapping of '{numericValue}' to enum 'ApplicationNamespace.Contracts.EBackingEnum'"), - }; + (var tryValue, value) = numericValue switch + { + 0 => (true, ApplicationNamespace.Contracts.EBackingEnum.A), + 1 => (true, ApplicationNamespace.Contracts.EBackingEnum.B), + _ => (false, default(ApplicationNamespace.Contracts.EBackingEnum)), + }; + + return tryValue; + } protected override System.Int32 ToBackingType( ApplicationNamespace.Contracts.EBackingEnum value diff --git a/src/Aviationexam.GeneratedJsonConverters.SourceGenerator.Tests/EnumJsonConverterIncrementalGeneratorSnapshotTests.EnumWithConfigurationWorks#EPropertyEnumEnumJsonConverter.g.verified.cs b/src/Aviationexam.GeneratedJsonConverters.SourceGenerator.Tests/EnumJsonConverterIncrementalGeneratorSnapshotTests.EnumWithConfigurationWorks#EPropertyEnumEnumJsonConverter.g.verified.cs index 2c2d55a..f14019f 100644 --- a/src/Aviationexam.GeneratedJsonConverters.SourceGenerator.Tests/EnumJsonConverterIncrementalGeneratorSnapshotTests.EnumWithConfigurationWorks#EPropertyEnumEnumJsonConverter.g.verified.cs +++ b/src/Aviationexam.GeneratedJsonConverters.SourceGenerator.Tests/EnumJsonConverterIncrementalGeneratorSnapshotTests.EnumWithConfigurationWorks#EPropertyEnumEnumJsonConverter.g.verified.cs @@ -11,26 +11,27 @@ internal class EPropertyEnumEnumJsonConverter : Aviationexam.GeneratedJsonConver protected override Aviationexam.GeneratedJsonConverters.EnumSerializationStrategy SerializationStrategy => Aviationexam.GeneratedJsonConverters.EnumSerializationStrategy.FirstEnumName; - protected override ApplicationNamespace.Contracts.EPropertyEnum ToEnum( - System.ReadOnlySpan enumName + protected override bool TryToEnum( + System.ReadOnlySpan enumName, out ApplicationNamespace.Contracts.EPropertyEnum value ) { if (System.MemoryExtensions.SequenceEqual(enumName, "C"u8)) { - return ApplicationNamespace.Contracts.EPropertyEnum.C; + value = ApplicationNamespace.Contracts.EPropertyEnum.C; + return true; } if (System.MemoryExtensions.SequenceEqual(enumName, "D"u8)) { - return ApplicationNamespace.Contracts.EPropertyEnum.D; + value = ApplicationNamespace.Contracts.EPropertyEnum.D; + return true; } - var stringValue = System.Text.Encoding.UTF8.GetString(enumName.ToArray()); - - throw new System.Text.Json.JsonException($"Undefined mapping of '{stringValue}' to enum 'ApplicationNamespace.Contracts.EPropertyEnum'"); + value = default(ApplicationNamespace.Contracts.EPropertyEnum); + return false; } - protected override ApplicationNamespace.Contracts.EPropertyEnum ToEnum( - System.Byte numericValue + protected override bool TryToEnum( + System.Byte numericValue, out ApplicationNamespace.Contracts.EPropertyEnum value ) => throw new System.Text.Json.JsonException("Enum is not configured to support deserialization from backing type"); protected override System.Byte ToBackingType( diff --git a/src/Aviationexam.GeneratedJsonConverters.SourceGenerator.Tests/EnumJsonConverterIncrementalGeneratorSnapshotTests.EnumWithConfigurationWorks#EPropertyWithBackingEnumEnumJsonConverter.g.verified.cs b/src/Aviationexam.GeneratedJsonConverters.SourceGenerator.Tests/EnumJsonConverterIncrementalGeneratorSnapshotTests.EnumWithConfigurationWorks#EPropertyWithBackingEnumEnumJsonConverter.g.verified.cs index d5fff2c..b02f1dd 100644 --- a/src/Aviationexam.GeneratedJsonConverters.SourceGenerator.Tests/EnumJsonConverterIncrementalGeneratorSnapshotTests.EnumWithConfigurationWorks#EPropertyWithBackingEnumEnumJsonConverter.g.verified.cs +++ b/src/Aviationexam.GeneratedJsonConverters.SourceGenerator.Tests/EnumJsonConverterIncrementalGeneratorSnapshotTests.EnumWithConfigurationWorks#EPropertyWithBackingEnumEnumJsonConverter.g.verified.cs @@ -11,32 +11,38 @@ internal class EPropertyWithBackingEnumEnumJsonConverter : Aviationexam.Generate protected override Aviationexam.GeneratedJsonConverters.EnumSerializationStrategy SerializationStrategy => Aviationexam.GeneratedJsonConverters.EnumSerializationStrategy.FirstEnumName; - protected override ApplicationNamespace.Contracts.EPropertyWithBackingEnum ToEnum( - System.ReadOnlySpan enumName + protected override bool TryToEnum( + System.ReadOnlySpan enumName, out ApplicationNamespace.Contracts.EPropertyWithBackingEnum value ) { if (System.MemoryExtensions.SequenceEqual(enumName, "E"u8)) { - return ApplicationNamespace.Contracts.EPropertyWithBackingEnum.E; + value = ApplicationNamespace.Contracts.EPropertyWithBackingEnum.E; + return true; } if (System.MemoryExtensions.SequenceEqual(enumName, "F"u8)) { - return ApplicationNamespace.Contracts.EPropertyWithBackingEnum.F; + value = ApplicationNamespace.Contracts.EPropertyWithBackingEnum.F; + return true; } - var stringValue = System.Text.Encoding.UTF8.GetString(enumName.ToArray()); - - throw new System.Text.Json.JsonException($"Undefined mapping of '{stringValue}' to enum 'ApplicationNamespace.Contracts.EPropertyWithBackingEnum'"); + value = default(ApplicationNamespace.Contracts.EPropertyWithBackingEnum); + return false; } - protected override ApplicationNamespace.Contracts.EPropertyWithBackingEnum ToEnum( - System.Int32 numericValue - ) => numericValue switch + protected override bool TryToEnum( + System.Int32 numericValue, out ApplicationNamespace.Contracts.EPropertyWithBackingEnum value + ) { - 0 => ApplicationNamespace.Contracts.EPropertyWithBackingEnum.E, - 1 => ApplicationNamespace.Contracts.EPropertyWithBackingEnum.F, - _ => throw new System.Text.Json.JsonException($"Undefined mapping of '{numericValue}' to enum 'ApplicationNamespace.Contracts.EPropertyWithBackingEnum'"), - }; + (var tryValue, value) = numericValue switch + { + 0 => (true, ApplicationNamespace.Contracts.EPropertyWithBackingEnum.E), + 1 => (true, ApplicationNamespace.Contracts.EPropertyWithBackingEnum.F), + _ => (false, default(ApplicationNamespace.Contracts.EPropertyWithBackingEnum)), + }; + + return tryValue; + } protected override System.Int32 ToBackingType( ApplicationNamespace.Contracts.EPropertyWithBackingEnum value diff --git a/src/Aviationexam.GeneratedJsonConverters.SourceGenerator.Tests/EnumJsonConverterIncrementalGeneratorSnapshotTests.EnumWithConfigurationWorks#EnumJsonConvertor.g.verified.cs b/src/Aviationexam.GeneratedJsonConverters.SourceGenerator.Tests/EnumJsonConverterIncrementalGeneratorSnapshotTests.EnumWithConfigurationWorks#EnumJsonConvertor.g.verified.cs index 1fc6eaa..34d3193 100644 --- a/src/Aviationexam.GeneratedJsonConverters.SourceGenerator.Tests/EnumJsonConverterIncrementalGeneratorSnapshotTests.EnumWithConfigurationWorks#EnumJsonConvertor.g.verified.cs +++ b/src/Aviationexam.GeneratedJsonConverters.SourceGenerator.Tests/EnumJsonConverterIncrementalGeneratorSnapshotTests.EnumWithConfigurationWorks#EnumJsonConvertor.g.verified.cs @@ -22,9 +22,9 @@ internal abstract class EnumJsonConvertor : JsonConverter protected abstract EnumSerializationStrategy SerializationStrategy { get; } - protected abstract T ToEnum(ReadOnlySpan enumName); + protected abstract bool TryToEnum(ReadOnlySpan enumName, out T value); - protected abstract T ToEnum(TBackingType numericValue); + protected abstract bool TryToEnum(TBackingType numericValue, out T value); protected abstract TBackingType ToBackingType(T value); @@ -41,7 +41,14 @@ reader.TokenType is JsonTokenType.String { var enumName = reader.ValueSpan; - return ToEnum(enumName); + if (TryToEnum(enumName, out var enumValue)) + { + return enumValue; + } + + var stringValue = Encoding.UTF8.GetString(enumName.ToArray()); + + throw new JsonException($"Undefined mapping of '{stringValue}' to enum '{typeof(T).FullName}'"); } if (reader.TokenType is JsonTokenType.Number) @@ -50,7 +57,12 @@ reader.TokenType is JsonTokenType.String if (numericValue.HasValue) { - return ToEnum(numericValue.Value); + if (TryToEnum(numericValue.Value, out var enumValue)) + { + return enumValue; + } + + throw new JsonException($"Undefined mapping of '{numericValue}' to enum '{{enumFullName}}'"); } } @@ -59,6 +71,44 @@ reader.TokenType is JsonTokenType.String throw new JsonException($"Unable to deserialize {value}('{reader.TokenType}') into {typeof(T).Name}"); } + public override T ReadAsPropertyName( + ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options + ) + { + if ( + reader.TokenType is JsonTokenType.PropertyName + && DeserializationStrategy.HasFlag(EnumDeserializationStrategy.UseEnumName) + ) + { + var enumName = reader.ValueSpan; + + if (TryToEnum(enumName, out var enumValue)) + { + return enumValue; + } + } + + var value = Encoding.UTF8.GetString(reader.ValueSpan.ToArray()); + + if ( + reader.TokenType is JsonTokenType.PropertyName + && DeserializationStrategy.HasFlag(EnumDeserializationStrategy.UseBackingType) + ) + { + var numericValue = ParseAsNumber(value); + + if (numericValue.HasValue) + { + if (TryToEnum(numericValue.Value, out var enumValue)) + { + return enumValue; + } + } + } + + throw new JsonException($"Unable to deserialize {value}('{reader.TokenType}') into {typeof(T).Name}"); + } + private TBackingType? ReadAsNumber(ref Utf8JsonReader reader) => BackingTypeTypeCode switch { TypeCode.SByte => reader.GetSByte() is var numericValue ? Unsafe.As(ref numericValue) : null, @@ -72,6 +122,21 @@ reader.TokenType is JsonTokenType.String _ => throw new ArgumentOutOfRangeException(nameof(BackingTypeTypeCode), BackingTypeTypeCode, $"Unexpected TypeCode {BackingTypeTypeCode}") }; + private TBackingType? ParseAsNumber( + string value + ) => BackingTypeTypeCode switch + { + TypeCode.SByte => sbyte.TryParse(value, out var numericValue) ? Unsafe.As(ref numericValue) : null, + TypeCode.Byte => byte.TryParse(value, out var numericValue) ? Unsafe.As(ref numericValue) : null, + TypeCode.Int16 => short.TryParse(value, out var numericValue) ? Unsafe.As(ref numericValue) : null, + TypeCode.UInt16 => ushort.TryParse(value, out var numericValue) ? Unsafe.As(ref numericValue) : null, + TypeCode.Int32 => int.TryParse(value, out var numericValue) ? Unsafe.As(ref numericValue) : null, + TypeCode.UInt32 => uint.TryParse(value, out var numericValue) ? Unsafe.As(ref numericValue) : null, + TypeCode.Int64 => long.TryParse(value, out var numericValue) ? Unsafe.As(ref numericValue) : null, + TypeCode.UInt64 => ulong.TryParse(value, out var numericValue) ? Unsafe.As(ref numericValue) : null, + _ => throw new ArgumentOutOfRangeException(nameof(BackingTypeTypeCode), BackingTypeTypeCode, $"Unexpected TypeCode {BackingTypeTypeCode}") + }; + public override void Write(Utf8JsonWriter writer, T value, JsonSerializerOptions options) { if (SerializationStrategy is EnumSerializationStrategy.BackingType) @@ -88,6 +153,22 @@ public override void Write(Utf8JsonWriter writer, T value, JsonSerializerOptions } } + public override void WriteAsPropertyName(Utf8JsonWriter writer, T value, JsonSerializerOptions options) + { + if (SerializationStrategy is EnumSerializationStrategy.BackingType) + { + WriteAsPropertyNameAsBackingType(writer, value, options); + } + else if (SerializationStrategy is EnumSerializationStrategy.FirstEnumName) + { + WriteAsPropertyNameAsFirstEnumName(writer, value, options); + } + else + { + throw new ArgumentOutOfRangeException(nameof(SerializationStrategy), SerializationStrategy, "Unknown serialization strategy"); + } + } + private void WriteAsBackingType( Utf8JsonWriter writer, T value, @@ -128,6 +209,18 @@ JsonSerializerOptions options } } + private void WriteAsPropertyNameAsBackingType( + Utf8JsonWriter writer, + T value, + [SuppressMessage("ReSharper", "UnusedParameter.Local")] + JsonSerializerOptions options + ) + { + var numericValue = ToBackingType(value); + + writer.WritePropertyName($"{numericValue}"); + } + private void WriteAsFirstEnumName( Utf8JsonWriter writer, T value, @@ -139,4 +232,16 @@ JsonSerializerOptions options writer.WriteStringValue(enumValue); } + + private void WriteAsPropertyNameAsFirstEnumName( + Utf8JsonWriter writer, + T value, + [SuppressMessage("ReSharper", "UnusedParameter.Local")] + JsonSerializerOptions options + ) + { + var enumValue = ToFirstEnumName(value); + + writer.WritePropertyName(enumValue); + } } diff --git a/src/Aviationexam.GeneratedJsonConverters.SourceGenerator.Tests/EnumJsonConverterIncrementalGeneratorSnapshotTests.EnumWithDuplicatedFieldWorks_BackingType#EMyEnumEnumJsonConverter.g.verified.cs b/src/Aviationexam.GeneratedJsonConverters.SourceGenerator.Tests/EnumJsonConverterIncrementalGeneratorSnapshotTests.EnumWithDuplicatedFieldWorks_BackingType#EMyEnumEnumJsonConverter.g.verified.cs index 0f8fd3d..598d86e 100644 --- a/src/Aviationexam.GeneratedJsonConverters.SourceGenerator.Tests/EnumJsonConverterIncrementalGeneratorSnapshotTests.EnumWithDuplicatedFieldWorks_BackingType#EMyEnumEnumJsonConverter.g.verified.cs +++ b/src/Aviationexam.GeneratedJsonConverters.SourceGenerator.Tests/EnumJsonConverterIncrementalGeneratorSnapshotTests.EnumWithDuplicatedFieldWorks_BackingType#EMyEnumEnumJsonConverter.g.verified.cs @@ -11,36 +11,43 @@ internal class EMyEnumEnumJsonConverter : Aviationexam.GeneratedJsonConverters.E protected override Aviationexam.GeneratedJsonConverters.EnumSerializationStrategy SerializationStrategy => Aviationexam.GeneratedJsonConverters.EnumSerializationStrategy.BackingType; - protected override ApplicationNamespace.Contracts.EMyEnum ToEnum( - System.ReadOnlySpan enumName + protected override bool TryToEnum( + System.ReadOnlySpan enumName, out ApplicationNamespace.Contracts.EMyEnum value ) { if (System.MemoryExtensions.SequenceEqual(enumName, "C"u8)) { - return ApplicationNamespace.Contracts.EMyEnum.A; + value = ApplicationNamespace.Contracts.EMyEnum.A; + return true; } if (System.MemoryExtensions.SequenceEqual(enumName, "D"u8)) { - return ApplicationNamespace.Contracts.EMyEnum.B; + value = ApplicationNamespace.Contracts.EMyEnum.B; + return true; } if (System.MemoryExtensions.SequenceEqual(enumName, "E"u8)) { - return ApplicationNamespace.Contracts.EMyEnum.C; + value = ApplicationNamespace.Contracts.EMyEnum.C; + return true; } - var stringValue = System.Text.Encoding.UTF8.GetString(enumName.ToArray()); - - throw new System.Text.Json.JsonException($"Undefined mapping of '{stringValue}' to enum 'ApplicationNamespace.Contracts.EMyEnum'"); + value = default(ApplicationNamespace.Contracts.EMyEnum); + return false; } - protected override ApplicationNamespace.Contracts.EMyEnum ToEnum( - System.Int32 numericValue - ) => numericValue switch + protected override bool TryToEnum( + System.Int32 numericValue, out ApplicationNamespace.Contracts.EMyEnum value + ) { - 0 => ApplicationNamespace.Contracts.EMyEnum.A, - 1 => ApplicationNamespace.Contracts.EMyEnum.B, - _ => throw new System.Text.Json.JsonException($"Undefined mapping of '{numericValue}' to enum 'ApplicationNamespace.Contracts.EMyEnum'"), - }; + (var tryValue, value) = numericValue switch + { + 0 => (true, ApplicationNamespace.Contracts.EMyEnum.A), + 1 => (true, ApplicationNamespace.Contracts.EMyEnum.B), + _ => (false, default(ApplicationNamespace.Contracts.EMyEnum)), + }; + + return tryValue; + } protected override System.Int32 ToBackingType( ApplicationNamespace.Contracts.EMyEnum value diff --git a/src/Aviationexam.GeneratedJsonConverters.SourceGenerator.Tests/EnumJsonConverterIncrementalGeneratorSnapshotTests.EnumWithDuplicatedFieldWorks_BackingType#EnumJsonConvertor.g.verified.cs b/src/Aviationexam.GeneratedJsonConverters.SourceGenerator.Tests/EnumJsonConverterIncrementalGeneratorSnapshotTests.EnumWithDuplicatedFieldWorks_BackingType#EnumJsonConvertor.g.verified.cs index 1fc6eaa..34d3193 100644 --- a/src/Aviationexam.GeneratedJsonConverters.SourceGenerator.Tests/EnumJsonConverterIncrementalGeneratorSnapshotTests.EnumWithDuplicatedFieldWorks_BackingType#EnumJsonConvertor.g.verified.cs +++ b/src/Aviationexam.GeneratedJsonConverters.SourceGenerator.Tests/EnumJsonConverterIncrementalGeneratorSnapshotTests.EnumWithDuplicatedFieldWorks_BackingType#EnumJsonConvertor.g.verified.cs @@ -22,9 +22,9 @@ internal abstract class EnumJsonConvertor : JsonConverter protected abstract EnumSerializationStrategy SerializationStrategy { get; } - protected abstract T ToEnum(ReadOnlySpan enumName); + protected abstract bool TryToEnum(ReadOnlySpan enumName, out T value); - protected abstract T ToEnum(TBackingType numericValue); + protected abstract bool TryToEnum(TBackingType numericValue, out T value); protected abstract TBackingType ToBackingType(T value); @@ -41,7 +41,14 @@ reader.TokenType is JsonTokenType.String { var enumName = reader.ValueSpan; - return ToEnum(enumName); + if (TryToEnum(enumName, out var enumValue)) + { + return enumValue; + } + + var stringValue = Encoding.UTF8.GetString(enumName.ToArray()); + + throw new JsonException($"Undefined mapping of '{stringValue}' to enum '{typeof(T).FullName}'"); } if (reader.TokenType is JsonTokenType.Number) @@ -50,7 +57,12 @@ reader.TokenType is JsonTokenType.String if (numericValue.HasValue) { - return ToEnum(numericValue.Value); + if (TryToEnum(numericValue.Value, out var enumValue)) + { + return enumValue; + } + + throw new JsonException($"Undefined mapping of '{numericValue}' to enum '{{enumFullName}}'"); } } @@ -59,6 +71,44 @@ reader.TokenType is JsonTokenType.String throw new JsonException($"Unable to deserialize {value}('{reader.TokenType}') into {typeof(T).Name}"); } + public override T ReadAsPropertyName( + ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options + ) + { + if ( + reader.TokenType is JsonTokenType.PropertyName + && DeserializationStrategy.HasFlag(EnumDeserializationStrategy.UseEnumName) + ) + { + var enumName = reader.ValueSpan; + + if (TryToEnum(enumName, out var enumValue)) + { + return enumValue; + } + } + + var value = Encoding.UTF8.GetString(reader.ValueSpan.ToArray()); + + if ( + reader.TokenType is JsonTokenType.PropertyName + && DeserializationStrategy.HasFlag(EnumDeserializationStrategy.UseBackingType) + ) + { + var numericValue = ParseAsNumber(value); + + if (numericValue.HasValue) + { + if (TryToEnum(numericValue.Value, out var enumValue)) + { + return enumValue; + } + } + } + + throw new JsonException($"Unable to deserialize {value}('{reader.TokenType}') into {typeof(T).Name}"); + } + private TBackingType? ReadAsNumber(ref Utf8JsonReader reader) => BackingTypeTypeCode switch { TypeCode.SByte => reader.GetSByte() is var numericValue ? Unsafe.As(ref numericValue) : null, @@ -72,6 +122,21 @@ reader.TokenType is JsonTokenType.String _ => throw new ArgumentOutOfRangeException(nameof(BackingTypeTypeCode), BackingTypeTypeCode, $"Unexpected TypeCode {BackingTypeTypeCode}") }; + private TBackingType? ParseAsNumber( + string value + ) => BackingTypeTypeCode switch + { + TypeCode.SByte => sbyte.TryParse(value, out var numericValue) ? Unsafe.As(ref numericValue) : null, + TypeCode.Byte => byte.TryParse(value, out var numericValue) ? Unsafe.As(ref numericValue) : null, + TypeCode.Int16 => short.TryParse(value, out var numericValue) ? Unsafe.As(ref numericValue) : null, + TypeCode.UInt16 => ushort.TryParse(value, out var numericValue) ? Unsafe.As(ref numericValue) : null, + TypeCode.Int32 => int.TryParse(value, out var numericValue) ? Unsafe.As(ref numericValue) : null, + TypeCode.UInt32 => uint.TryParse(value, out var numericValue) ? Unsafe.As(ref numericValue) : null, + TypeCode.Int64 => long.TryParse(value, out var numericValue) ? Unsafe.As(ref numericValue) : null, + TypeCode.UInt64 => ulong.TryParse(value, out var numericValue) ? Unsafe.As(ref numericValue) : null, + _ => throw new ArgumentOutOfRangeException(nameof(BackingTypeTypeCode), BackingTypeTypeCode, $"Unexpected TypeCode {BackingTypeTypeCode}") + }; + public override void Write(Utf8JsonWriter writer, T value, JsonSerializerOptions options) { if (SerializationStrategy is EnumSerializationStrategy.BackingType) @@ -88,6 +153,22 @@ public override void Write(Utf8JsonWriter writer, T value, JsonSerializerOptions } } + public override void WriteAsPropertyName(Utf8JsonWriter writer, T value, JsonSerializerOptions options) + { + if (SerializationStrategy is EnumSerializationStrategy.BackingType) + { + WriteAsPropertyNameAsBackingType(writer, value, options); + } + else if (SerializationStrategy is EnumSerializationStrategy.FirstEnumName) + { + WriteAsPropertyNameAsFirstEnumName(writer, value, options); + } + else + { + throw new ArgumentOutOfRangeException(nameof(SerializationStrategy), SerializationStrategy, "Unknown serialization strategy"); + } + } + private void WriteAsBackingType( Utf8JsonWriter writer, T value, @@ -128,6 +209,18 @@ JsonSerializerOptions options } } + private void WriteAsPropertyNameAsBackingType( + Utf8JsonWriter writer, + T value, + [SuppressMessage("ReSharper", "UnusedParameter.Local")] + JsonSerializerOptions options + ) + { + var numericValue = ToBackingType(value); + + writer.WritePropertyName($"{numericValue}"); + } + private void WriteAsFirstEnumName( Utf8JsonWriter writer, T value, @@ -139,4 +232,16 @@ JsonSerializerOptions options writer.WriteStringValue(enumValue); } + + private void WriteAsPropertyNameAsFirstEnumName( + Utf8JsonWriter writer, + T value, + [SuppressMessage("ReSharper", "UnusedParameter.Local")] + JsonSerializerOptions options + ) + { + var enumValue = ToFirstEnumName(value); + + writer.WritePropertyName(enumValue); + } } diff --git a/src/Aviationexam.GeneratedJsonConverters.SourceGenerator.Tests/EnumJsonConverterIncrementalGeneratorSnapshotTests.EnumWithDuplicatedFieldWorks_FirstEnumName#EMyEnumEnumJsonConverter.g.verified.cs b/src/Aviationexam.GeneratedJsonConverters.SourceGenerator.Tests/EnumJsonConverterIncrementalGeneratorSnapshotTests.EnumWithDuplicatedFieldWorks_FirstEnumName#EMyEnumEnumJsonConverter.g.verified.cs index 0f8fd3d..598d86e 100644 --- a/src/Aviationexam.GeneratedJsonConverters.SourceGenerator.Tests/EnumJsonConverterIncrementalGeneratorSnapshotTests.EnumWithDuplicatedFieldWorks_FirstEnumName#EMyEnumEnumJsonConverter.g.verified.cs +++ b/src/Aviationexam.GeneratedJsonConverters.SourceGenerator.Tests/EnumJsonConverterIncrementalGeneratorSnapshotTests.EnumWithDuplicatedFieldWorks_FirstEnumName#EMyEnumEnumJsonConverter.g.verified.cs @@ -11,36 +11,43 @@ internal class EMyEnumEnumJsonConverter : Aviationexam.GeneratedJsonConverters.E protected override Aviationexam.GeneratedJsonConverters.EnumSerializationStrategy SerializationStrategy => Aviationexam.GeneratedJsonConverters.EnumSerializationStrategy.BackingType; - protected override ApplicationNamespace.Contracts.EMyEnum ToEnum( - System.ReadOnlySpan enumName + protected override bool TryToEnum( + System.ReadOnlySpan enumName, out ApplicationNamespace.Contracts.EMyEnum value ) { if (System.MemoryExtensions.SequenceEqual(enumName, "C"u8)) { - return ApplicationNamespace.Contracts.EMyEnum.A; + value = ApplicationNamespace.Contracts.EMyEnum.A; + return true; } if (System.MemoryExtensions.SequenceEqual(enumName, "D"u8)) { - return ApplicationNamespace.Contracts.EMyEnum.B; + value = ApplicationNamespace.Contracts.EMyEnum.B; + return true; } if (System.MemoryExtensions.SequenceEqual(enumName, "E"u8)) { - return ApplicationNamespace.Contracts.EMyEnum.C; + value = ApplicationNamespace.Contracts.EMyEnum.C; + return true; } - var stringValue = System.Text.Encoding.UTF8.GetString(enumName.ToArray()); - - throw new System.Text.Json.JsonException($"Undefined mapping of '{stringValue}' to enum 'ApplicationNamespace.Contracts.EMyEnum'"); + value = default(ApplicationNamespace.Contracts.EMyEnum); + return false; } - protected override ApplicationNamespace.Contracts.EMyEnum ToEnum( - System.Int32 numericValue - ) => numericValue switch + protected override bool TryToEnum( + System.Int32 numericValue, out ApplicationNamespace.Contracts.EMyEnum value + ) { - 0 => ApplicationNamespace.Contracts.EMyEnum.A, - 1 => ApplicationNamespace.Contracts.EMyEnum.B, - _ => throw new System.Text.Json.JsonException($"Undefined mapping of '{numericValue}' to enum 'ApplicationNamespace.Contracts.EMyEnum'"), - }; + (var tryValue, value) = numericValue switch + { + 0 => (true, ApplicationNamespace.Contracts.EMyEnum.A), + 1 => (true, ApplicationNamespace.Contracts.EMyEnum.B), + _ => (false, default(ApplicationNamespace.Contracts.EMyEnum)), + }; + + return tryValue; + } protected override System.Int32 ToBackingType( ApplicationNamespace.Contracts.EMyEnum value diff --git a/src/Aviationexam.GeneratedJsonConverters.SourceGenerator.Tests/EnumJsonConverterIncrementalGeneratorSnapshotTests.EnumWithDuplicatedFieldWorks_FirstEnumName#EnumJsonConvertor.g.verified.cs b/src/Aviationexam.GeneratedJsonConverters.SourceGenerator.Tests/EnumJsonConverterIncrementalGeneratorSnapshotTests.EnumWithDuplicatedFieldWorks_FirstEnumName#EnumJsonConvertor.g.verified.cs index 1fc6eaa..34d3193 100644 --- a/src/Aviationexam.GeneratedJsonConverters.SourceGenerator.Tests/EnumJsonConverterIncrementalGeneratorSnapshotTests.EnumWithDuplicatedFieldWorks_FirstEnumName#EnumJsonConvertor.g.verified.cs +++ b/src/Aviationexam.GeneratedJsonConverters.SourceGenerator.Tests/EnumJsonConverterIncrementalGeneratorSnapshotTests.EnumWithDuplicatedFieldWorks_FirstEnumName#EnumJsonConvertor.g.verified.cs @@ -22,9 +22,9 @@ internal abstract class EnumJsonConvertor : JsonConverter protected abstract EnumSerializationStrategy SerializationStrategy { get; } - protected abstract T ToEnum(ReadOnlySpan enumName); + protected abstract bool TryToEnum(ReadOnlySpan enumName, out T value); - protected abstract T ToEnum(TBackingType numericValue); + protected abstract bool TryToEnum(TBackingType numericValue, out T value); protected abstract TBackingType ToBackingType(T value); @@ -41,7 +41,14 @@ reader.TokenType is JsonTokenType.String { var enumName = reader.ValueSpan; - return ToEnum(enumName); + if (TryToEnum(enumName, out var enumValue)) + { + return enumValue; + } + + var stringValue = Encoding.UTF8.GetString(enumName.ToArray()); + + throw new JsonException($"Undefined mapping of '{stringValue}' to enum '{typeof(T).FullName}'"); } if (reader.TokenType is JsonTokenType.Number) @@ -50,7 +57,12 @@ reader.TokenType is JsonTokenType.String if (numericValue.HasValue) { - return ToEnum(numericValue.Value); + if (TryToEnum(numericValue.Value, out var enumValue)) + { + return enumValue; + } + + throw new JsonException($"Undefined mapping of '{numericValue}' to enum '{{enumFullName}}'"); } } @@ -59,6 +71,44 @@ reader.TokenType is JsonTokenType.String throw new JsonException($"Unable to deserialize {value}('{reader.TokenType}') into {typeof(T).Name}"); } + public override T ReadAsPropertyName( + ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options + ) + { + if ( + reader.TokenType is JsonTokenType.PropertyName + && DeserializationStrategy.HasFlag(EnumDeserializationStrategy.UseEnumName) + ) + { + var enumName = reader.ValueSpan; + + if (TryToEnum(enumName, out var enumValue)) + { + return enumValue; + } + } + + var value = Encoding.UTF8.GetString(reader.ValueSpan.ToArray()); + + if ( + reader.TokenType is JsonTokenType.PropertyName + && DeserializationStrategy.HasFlag(EnumDeserializationStrategy.UseBackingType) + ) + { + var numericValue = ParseAsNumber(value); + + if (numericValue.HasValue) + { + if (TryToEnum(numericValue.Value, out var enumValue)) + { + return enumValue; + } + } + } + + throw new JsonException($"Unable to deserialize {value}('{reader.TokenType}') into {typeof(T).Name}"); + } + private TBackingType? ReadAsNumber(ref Utf8JsonReader reader) => BackingTypeTypeCode switch { TypeCode.SByte => reader.GetSByte() is var numericValue ? Unsafe.As(ref numericValue) : null, @@ -72,6 +122,21 @@ reader.TokenType is JsonTokenType.String _ => throw new ArgumentOutOfRangeException(nameof(BackingTypeTypeCode), BackingTypeTypeCode, $"Unexpected TypeCode {BackingTypeTypeCode}") }; + private TBackingType? ParseAsNumber( + string value + ) => BackingTypeTypeCode switch + { + TypeCode.SByte => sbyte.TryParse(value, out var numericValue) ? Unsafe.As(ref numericValue) : null, + TypeCode.Byte => byte.TryParse(value, out var numericValue) ? Unsafe.As(ref numericValue) : null, + TypeCode.Int16 => short.TryParse(value, out var numericValue) ? Unsafe.As(ref numericValue) : null, + TypeCode.UInt16 => ushort.TryParse(value, out var numericValue) ? Unsafe.As(ref numericValue) : null, + TypeCode.Int32 => int.TryParse(value, out var numericValue) ? Unsafe.As(ref numericValue) : null, + TypeCode.UInt32 => uint.TryParse(value, out var numericValue) ? Unsafe.As(ref numericValue) : null, + TypeCode.Int64 => long.TryParse(value, out var numericValue) ? Unsafe.As(ref numericValue) : null, + TypeCode.UInt64 => ulong.TryParse(value, out var numericValue) ? Unsafe.As(ref numericValue) : null, + _ => throw new ArgumentOutOfRangeException(nameof(BackingTypeTypeCode), BackingTypeTypeCode, $"Unexpected TypeCode {BackingTypeTypeCode}") + }; + public override void Write(Utf8JsonWriter writer, T value, JsonSerializerOptions options) { if (SerializationStrategy is EnumSerializationStrategy.BackingType) @@ -88,6 +153,22 @@ public override void Write(Utf8JsonWriter writer, T value, JsonSerializerOptions } } + public override void WriteAsPropertyName(Utf8JsonWriter writer, T value, JsonSerializerOptions options) + { + if (SerializationStrategy is EnumSerializationStrategy.BackingType) + { + WriteAsPropertyNameAsBackingType(writer, value, options); + } + else if (SerializationStrategy is EnumSerializationStrategy.FirstEnumName) + { + WriteAsPropertyNameAsFirstEnumName(writer, value, options); + } + else + { + throw new ArgumentOutOfRangeException(nameof(SerializationStrategy), SerializationStrategy, "Unknown serialization strategy"); + } + } + private void WriteAsBackingType( Utf8JsonWriter writer, T value, @@ -128,6 +209,18 @@ JsonSerializerOptions options } } + private void WriteAsPropertyNameAsBackingType( + Utf8JsonWriter writer, + T value, + [SuppressMessage("ReSharper", "UnusedParameter.Local")] + JsonSerializerOptions options + ) + { + var numericValue = ToBackingType(value); + + writer.WritePropertyName($"{numericValue}"); + } + private void WriteAsFirstEnumName( Utf8JsonWriter writer, T value, @@ -139,4 +232,16 @@ JsonSerializerOptions options writer.WriteStringValue(enumValue); } + + private void WriteAsPropertyNameAsFirstEnumName( + Utf8JsonWriter writer, + T value, + [SuppressMessage("ReSharper", "UnusedParameter.Local")] + JsonSerializerOptions options + ) + { + var enumValue = ToFirstEnumName(value); + + writer.WritePropertyName(enumValue); + } } diff --git a/src/Aviationexam.GeneratedJsonConverters.SourceGenerator.Tests/EnumJsonConverterIncrementalGeneratorSnapshotTests.EnumWithEnumMemberWorks#EMyEnumEnumJsonConverter.g.verified.cs b/src/Aviationexam.GeneratedJsonConverters.SourceGenerator.Tests/EnumJsonConverterIncrementalGeneratorSnapshotTests.EnumWithEnumMemberWorks#EMyEnumEnumJsonConverter.g.verified.cs index cde85de..99a025e 100644 --- a/src/Aviationexam.GeneratedJsonConverters.SourceGenerator.Tests/EnumJsonConverterIncrementalGeneratorSnapshotTests.EnumWithEnumMemberWorks#EMyEnumEnumJsonConverter.g.verified.cs +++ b/src/Aviationexam.GeneratedJsonConverters.SourceGenerator.Tests/EnumJsonConverterIncrementalGeneratorSnapshotTests.EnumWithEnumMemberWorks#EMyEnumEnumJsonConverter.g.verified.cs @@ -11,26 +11,27 @@ internal class EMyEnumEnumJsonConverter : Aviationexam.GeneratedJsonConverters.E protected override Aviationexam.GeneratedJsonConverters.EnumSerializationStrategy SerializationStrategy => Aviationexam.GeneratedJsonConverters.EnumSerializationStrategy.FirstEnumName; - protected override ApplicationNamespace.Contracts.EMyEnum ToEnum( - System.ReadOnlySpan enumName + protected override bool TryToEnum( + System.ReadOnlySpan enumName, out ApplicationNamespace.Contracts.EMyEnum value ) { if (System.MemoryExtensions.SequenceEqual(enumName, "C"u8)) { - return ApplicationNamespace.Contracts.EMyEnum.A; + value = ApplicationNamespace.Contracts.EMyEnum.A; + return true; } if (System.MemoryExtensions.SequenceEqual(enumName, "D"u8)) { - return ApplicationNamespace.Contracts.EMyEnum.B; + value = ApplicationNamespace.Contracts.EMyEnum.B; + return true; } - var stringValue = System.Text.Encoding.UTF8.GetString(enumName.ToArray()); - - throw new System.Text.Json.JsonException($"Undefined mapping of '{stringValue}' to enum 'ApplicationNamespace.Contracts.EMyEnum'"); + value = default(ApplicationNamespace.Contracts.EMyEnum); + return false; } - protected override ApplicationNamespace.Contracts.EMyEnum ToEnum( - System.Int32 numericValue + protected override bool TryToEnum( + System.Int32 numericValue, out ApplicationNamespace.Contracts.EMyEnum value ) => throw new System.Text.Json.JsonException("Enum is not configured to support deserialization from backing type"); protected override System.Int32 ToBackingType( diff --git a/src/Aviationexam.GeneratedJsonConverters.SourceGenerator.Tests/EnumJsonConverterIncrementalGeneratorSnapshotTests.EnumWithEnumMemberWorks#EnumJsonConvertor.g.verified.cs b/src/Aviationexam.GeneratedJsonConverters.SourceGenerator.Tests/EnumJsonConverterIncrementalGeneratorSnapshotTests.EnumWithEnumMemberWorks#EnumJsonConvertor.g.verified.cs index 1fc6eaa..34d3193 100644 --- a/src/Aviationexam.GeneratedJsonConverters.SourceGenerator.Tests/EnumJsonConverterIncrementalGeneratorSnapshotTests.EnumWithEnumMemberWorks#EnumJsonConvertor.g.verified.cs +++ b/src/Aviationexam.GeneratedJsonConverters.SourceGenerator.Tests/EnumJsonConverterIncrementalGeneratorSnapshotTests.EnumWithEnumMemberWorks#EnumJsonConvertor.g.verified.cs @@ -22,9 +22,9 @@ internal abstract class EnumJsonConvertor : JsonConverter protected abstract EnumSerializationStrategy SerializationStrategy { get; } - protected abstract T ToEnum(ReadOnlySpan enumName); + protected abstract bool TryToEnum(ReadOnlySpan enumName, out T value); - protected abstract T ToEnum(TBackingType numericValue); + protected abstract bool TryToEnum(TBackingType numericValue, out T value); protected abstract TBackingType ToBackingType(T value); @@ -41,7 +41,14 @@ reader.TokenType is JsonTokenType.String { var enumName = reader.ValueSpan; - return ToEnum(enumName); + if (TryToEnum(enumName, out var enumValue)) + { + return enumValue; + } + + var stringValue = Encoding.UTF8.GetString(enumName.ToArray()); + + throw new JsonException($"Undefined mapping of '{stringValue}' to enum '{typeof(T).FullName}'"); } if (reader.TokenType is JsonTokenType.Number) @@ -50,7 +57,12 @@ reader.TokenType is JsonTokenType.String if (numericValue.HasValue) { - return ToEnum(numericValue.Value); + if (TryToEnum(numericValue.Value, out var enumValue)) + { + return enumValue; + } + + throw new JsonException($"Undefined mapping of '{numericValue}' to enum '{{enumFullName}}'"); } } @@ -59,6 +71,44 @@ reader.TokenType is JsonTokenType.String throw new JsonException($"Unable to deserialize {value}('{reader.TokenType}') into {typeof(T).Name}"); } + public override T ReadAsPropertyName( + ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options + ) + { + if ( + reader.TokenType is JsonTokenType.PropertyName + && DeserializationStrategy.HasFlag(EnumDeserializationStrategy.UseEnumName) + ) + { + var enumName = reader.ValueSpan; + + if (TryToEnum(enumName, out var enumValue)) + { + return enumValue; + } + } + + var value = Encoding.UTF8.GetString(reader.ValueSpan.ToArray()); + + if ( + reader.TokenType is JsonTokenType.PropertyName + && DeserializationStrategy.HasFlag(EnumDeserializationStrategy.UseBackingType) + ) + { + var numericValue = ParseAsNumber(value); + + if (numericValue.HasValue) + { + if (TryToEnum(numericValue.Value, out var enumValue)) + { + return enumValue; + } + } + } + + throw new JsonException($"Unable to deserialize {value}('{reader.TokenType}') into {typeof(T).Name}"); + } + private TBackingType? ReadAsNumber(ref Utf8JsonReader reader) => BackingTypeTypeCode switch { TypeCode.SByte => reader.GetSByte() is var numericValue ? Unsafe.As(ref numericValue) : null, @@ -72,6 +122,21 @@ reader.TokenType is JsonTokenType.String _ => throw new ArgumentOutOfRangeException(nameof(BackingTypeTypeCode), BackingTypeTypeCode, $"Unexpected TypeCode {BackingTypeTypeCode}") }; + private TBackingType? ParseAsNumber( + string value + ) => BackingTypeTypeCode switch + { + TypeCode.SByte => sbyte.TryParse(value, out var numericValue) ? Unsafe.As(ref numericValue) : null, + TypeCode.Byte => byte.TryParse(value, out var numericValue) ? Unsafe.As(ref numericValue) : null, + TypeCode.Int16 => short.TryParse(value, out var numericValue) ? Unsafe.As(ref numericValue) : null, + TypeCode.UInt16 => ushort.TryParse(value, out var numericValue) ? Unsafe.As(ref numericValue) : null, + TypeCode.Int32 => int.TryParse(value, out var numericValue) ? Unsafe.As(ref numericValue) : null, + TypeCode.UInt32 => uint.TryParse(value, out var numericValue) ? Unsafe.As(ref numericValue) : null, + TypeCode.Int64 => long.TryParse(value, out var numericValue) ? Unsafe.As(ref numericValue) : null, + TypeCode.UInt64 => ulong.TryParse(value, out var numericValue) ? Unsafe.As(ref numericValue) : null, + _ => throw new ArgumentOutOfRangeException(nameof(BackingTypeTypeCode), BackingTypeTypeCode, $"Unexpected TypeCode {BackingTypeTypeCode}") + }; + public override void Write(Utf8JsonWriter writer, T value, JsonSerializerOptions options) { if (SerializationStrategy is EnumSerializationStrategy.BackingType) @@ -88,6 +153,22 @@ public override void Write(Utf8JsonWriter writer, T value, JsonSerializerOptions } } + public override void WriteAsPropertyName(Utf8JsonWriter writer, T value, JsonSerializerOptions options) + { + if (SerializationStrategy is EnumSerializationStrategy.BackingType) + { + WriteAsPropertyNameAsBackingType(writer, value, options); + } + else if (SerializationStrategy is EnumSerializationStrategy.FirstEnumName) + { + WriteAsPropertyNameAsFirstEnumName(writer, value, options); + } + else + { + throw new ArgumentOutOfRangeException(nameof(SerializationStrategy), SerializationStrategy, "Unknown serialization strategy"); + } + } + private void WriteAsBackingType( Utf8JsonWriter writer, T value, @@ -128,6 +209,18 @@ JsonSerializerOptions options } } + private void WriteAsPropertyNameAsBackingType( + Utf8JsonWriter writer, + T value, + [SuppressMessage("ReSharper", "UnusedParameter.Local")] + JsonSerializerOptions options + ) + { + var numericValue = ToBackingType(value); + + writer.WritePropertyName($"{numericValue}"); + } + private void WriteAsFirstEnumName( Utf8JsonWriter writer, T value, @@ -139,4 +232,16 @@ JsonSerializerOptions options writer.WriteStringValue(enumValue); } + + private void WriteAsPropertyNameAsFirstEnumName( + Utf8JsonWriter writer, + T value, + [SuppressMessage("ReSharper", "UnusedParameter.Local")] + JsonSerializerOptions options + ) + { + var enumValue = ToFirstEnumName(value); + + writer.WritePropertyName(enumValue); + } } diff --git a/src/Aviationexam.GeneratedJsonConverters.SourceGenerator.Tests/EnumJsonConverterIncrementalGeneratorSnapshotTests.NotAnnotatedEnumReportingWorks#EnumJsonConvertor.g.verified.cs b/src/Aviationexam.GeneratedJsonConverters.SourceGenerator.Tests/EnumJsonConverterIncrementalGeneratorSnapshotTests.NotAnnotatedEnumReportingWorks#EnumJsonConvertor.g.verified.cs index 1fc6eaa..34d3193 100644 --- a/src/Aviationexam.GeneratedJsonConverters.SourceGenerator.Tests/EnumJsonConverterIncrementalGeneratorSnapshotTests.NotAnnotatedEnumReportingWorks#EnumJsonConvertor.g.verified.cs +++ b/src/Aviationexam.GeneratedJsonConverters.SourceGenerator.Tests/EnumJsonConverterIncrementalGeneratorSnapshotTests.NotAnnotatedEnumReportingWorks#EnumJsonConvertor.g.verified.cs @@ -22,9 +22,9 @@ internal abstract class EnumJsonConvertor : JsonConverter protected abstract EnumSerializationStrategy SerializationStrategy { get; } - protected abstract T ToEnum(ReadOnlySpan enumName); + protected abstract bool TryToEnum(ReadOnlySpan enumName, out T value); - protected abstract T ToEnum(TBackingType numericValue); + protected abstract bool TryToEnum(TBackingType numericValue, out T value); protected abstract TBackingType ToBackingType(T value); @@ -41,7 +41,14 @@ reader.TokenType is JsonTokenType.String { var enumName = reader.ValueSpan; - return ToEnum(enumName); + if (TryToEnum(enumName, out var enumValue)) + { + return enumValue; + } + + var stringValue = Encoding.UTF8.GetString(enumName.ToArray()); + + throw new JsonException($"Undefined mapping of '{stringValue}' to enum '{typeof(T).FullName}'"); } if (reader.TokenType is JsonTokenType.Number) @@ -50,7 +57,12 @@ reader.TokenType is JsonTokenType.String if (numericValue.HasValue) { - return ToEnum(numericValue.Value); + if (TryToEnum(numericValue.Value, out var enumValue)) + { + return enumValue; + } + + throw new JsonException($"Undefined mapping of '{numericValue}' to enum '{{enumFullName}}'"); } } @@ -59,6 +71,44 @@ reader.TokenType is JsonTokenType.String throw new JsonException($"Unable to deserialize {value}('{reader.TokenType}') into {typeof(T).Name}"); } + public override T ReadAsPropertyName( + ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options + ) + { + if ( + reader.TokenType is JsonTokenType.PropertyName + && DeserializationStrategy.HasFlag(EnumDeserializationStrategy.UseEnumName) + ) + { + var enumName = reader.ValueSpan; + + if (TryToEnum(enumName, out var enumValue)) + { + return enumValue; + } + } + + var value = Encoding.UTF8.GetString(reader.ValueSpan.ToArray()); + + if ( + reader.TokenType is JsonTokenType.PropertyName + && DeserializationStrategy.HasFlag(EnumDeserializationStrategy.UseBackingType) + ) + { + var numericValue = ParseAsNumber(value); + + if (numericValue.HasValue) + { + if (TryToEnum(numericValue.Value, out var enumValue)) + { + return enumValue; + } + } + } + + throw new JsonException($"Unable to deserialize {value}('{reader.TokenType}') into {typeof(T).Name}"); + } + private TBackingType? ReadAsNumber(ref Utf8JsonReader reader) => BackingTypeTypeCode switch { TypeCode.SByte => reader.GetSByte() is var numericValue ? Unsafe.As(ref numericValue) : null, @@ -72,6 +122,21 @@ reader.TokenType is JsonTokenType.String _ => throw new ArgumentOutOfRangeException(nameof(BackingTypeTypeCode), BackingTypeTypeCode, $"Unexpected TypeCode {BackingTypeTypeCode}") }; + private TBackingType? ParseAsNumber( + string value + ) => BackingTypeTypeCode switch + { + TypeCode.SByte => sbyte.TryParse(value, out var numericValue) ? Unsafe.As(ref numericValue) : null, + TypeCode.Byte => byte.TryParse(value, out var numericValue) ? Unsafe.As(ref numericValue) : null, + TypeCode.Int16 => short.TryParse(value, out var numericValue) ? Unsafe.As(ref numericValue) : null, + TypeCode.UInt16 => ushort.TryParse(value, out var numericValue) ? Unsafe.As(ref numericValue) : null, + TypeCode.Int32 => int.TryParse(value, out var numericValue) ? Unsafe.As(ref numericValue) : null, + TypeCode.UInt32 => uint.TryParse(value, out var numericValue) ? Unsafe.As(ref numericValue) : null, + TypeCode.Int64 => long.TryParse(value, out var numericValue) ? Unsafe.As(ref numericValue) : null, + TypeCode.UInt64 => ulong.TryParse(value, out var numericValue) ? Unsafe.As(ref numericValue) : null, + _ => throw new ArgumentOutOfRangeException(nameof(BackingTypeTypeCode), BackingTypeTypeCode, $"Unexpected TypeCode {BackingTypeTypeCode}") + }; + public override void Write(Utf8JsonWriter writer, T value, JsonSerializerOptions options) { if (SerializationStrategy is EnumSerializationStrategy.BackingType) @@ -88,6 +153,22 @@ public override void Write(Utf8JsonWriter writer, T value, JsonSerializerOptions } } + public override void WriteAsPropertyName(Utf8JsonWriter writer, T value, JsonSerializerOptions options) + { + if (SerializationStrategy is EnumSerializationStrategy.BackingType) + { + WriteAsPropertyNameAsBackingType(writer, value, options); + } + else if (SerializationStrategy is EnumSerializationStrategy.FirstEnumName) + { + WriteAsPropertyNameAsFirstEnumName(writer, value, options); + } + else + { + throw new ArgumentOutOfRangeException(nameof(SerializationStrategy), SerializationStrategy, "Unknown serialization strategy"); + } + } + private void WriteAsBackingType( Utf8JsonWriter writer, T value, @@ -128,6 +209,18 @@ JsonSerializerOptions options } } + private void WriteAsPropertyNameAsBackingType( + Utf8JsonWriter writer, + T value, + [SuppressMessage("ReSharper", "UnusedParameter.Local")] + JsonSerializerOptions options + ) + { + var numericValue = ToBackingType(value); + + writer.WritePropertyName($"{numericValue}"); + } + private void WriteAsFirstEnumName( Utf8JsonWriter writer, T value, @@ -139,4 +232,16 @@ JsonSerializerOptions options writer.WriteStringValue(enumValue); } + + private void WriteAsPropertyNameAsFirstEnumName( + Utf8JsonWriter writer, + T value, + [SuppressMessage("ReSharper", "UnusedParameter.Local")] + JsonSerializerOptions options + ) + { + var enumValue = ToFirstEnumName(value); + + writer.WritePropertyName(enumValue); + } } diff --git a/src/Aviationexam.GeneratedJsonConverters.SourceGenerator.Tests/EnumJsonConverterIncrementalGeneratorSnapshotTests.ProjectConfigurationWorks_UseBackingType#EMyEnumEnumJsonConverter.g.verified.cs b/src/Aviationexam.GeneratedJsonConverters.SourceGenerator.Tests/EnumJsonConverterIncrementalGeneratorSnapshotTests.ProjectConfigurationWorks_UseBackingType#EMyEnumEnumJsonConverter.g.verified.cs index 2bad3f2..32bb098 100644 --- a/src/Aviationexam.GeneratedJsonConverters.SourceGenerator.Tests/EnumJsonConverterIncrementalGeneratorSnapshotTests.ProjectConfigurationWorks_UseBackingType#EMyEnumEnumJsonConverter.g.verified.cs +++ b/src/Aviationexam.GeneratedJsonConverters.SourceGenerator.Tests/EnumJsonConverterIncrementalGeneratorSnapshotTests.ProjectConfigurationWorks_UseBackingType#EMyEnumEnumJsonConverter.g.verified.cs @@ -11,18 +11,23 @@ internal class EMyEnumEnumJsonConverter : Aviationexam.GeneratedJsonConverters.E protected override Aviationexam.GeneratedJsonConverters.EnumSerializationStrategy SerializationStrategy => Aviationexam.GeneratedJsonConverters.EnumSerializationStrategy.BackingType; - protected override ApplicationNamespace.Contracts.EMyEnum ToEnum( - System.ReadOnlySpan enumName + protected override bool TryToEnum( + System.ReadOnlySpan enumName, out ApplicationNamespace.Contracts.EMyEnum value ) => throw new System.Text.Json.JsonException("Enum is not configured to support deserialization from enum name"); - protected override ApplicationNamespace.Contracts.EMyEnum ToEnum( - System.Int32 numericValue - ) => numericValue switch + protected override bool TryToEnum( + System.Int32 numericValue, out ApplicationNamespace.Contracts.EMyEnum value + ) { - 0 => ApplicationNamespace.Contracts.EMyEnum.A, - 1 => ApplicationNamespace.Contracts.EMyEnum.B, - _ => throw new System.Text.Json.JsonException($"Undefined mapping of '{numericValue}' to enum 'ApplicationNamespace.Contracts.EMyEnum'"), - }; + (var tryValue, value) = numericValue switch + { + 0 => (true, ApplicationNamespace.Contracts.EMyEnum.A), + 1 => (true, ApplicationNamespace.Contracts.EMyEnum.B), + _ => (false, default(ApplicationNamespace.Contracts.EMyEnum)), + }; + + return tryValue; + } protected override System.Int32 ToBackingType( ApplicationNamespace.Contracts.EMyEnum value @@ -36,4 +41,4 @@ ApplicationNamespace.Contracts.EMyEnum value protected override System.ReadOnlySpan ToFirstEnumName( ApplicationNamespace.Contracts.EMyEnum value ) => throw new System.Text.Json.JsonException("Enum is not configured to support serialization to enum type"); -} \ No newline at end of file +} diff --git a/src/Aviationexam.GeneratedJsonConverters.SourceGenerator.Tests/EnumJsonConverterIncrementalGeneratorSnapshotTests.ProjectConfigurationWorks_UseBackingType#EnumJsonConvertor.g.verified.cs b/src/Aviationexam.GeneratedJsonConverters.SourceGenerator.Tests/EnumJsonConverterIncrementalGeneratorSnapshotTests.ProjectConfigurationWorks_UseBackingType#EnumJsonConvertor.g.verified.cs index 1fc6eaa..34d3193 100644 --- a/src/Aviationexam.GeneratedJsonConverters.SourceGenerator.Tests/EnumJsonConverterIncrementalGeneratorSnapshotTests.ProjectConfigurationWorks_UseBackingType#EnumJsonConvertor.g.verified.cs +++ b/src/Aviationexam.GeneratedJsonConverters.SourceGenerator.Tests/EnumJsonConverterIncrementalGeneratorSnapshotTests.ProjectConfigurationWorks_UseBackingType#EnumJsonConvertor.g.verified.cs @@ -22,9 +22,9 @@ internal abstract class EnumJsonConvertor : JsonConverter protected abstract EnumSerializationStrategy SerializationStrategy { get; } - protected abstract T ToEnum(ReadOnlySpan enumName); + protected abstract bool TryToEnum(ReadOnlySpan enumName, out T value); - protected abstract T ToEnum(TBackingType numericValue); + protected abstract bool TryToEnum(TBackingType numericValue, out T value); protected abstract TBackingType ToBackingType(T value); @@ -41,7 +41,14 @@ reader.TokenType is JsonTokenType.String { var enumName = reader.ValueSpan; - return ToEnum(enumName); + if (TryToEnum(enumName, out var enumValue)) + { + return enumValue; + } + + var stringValue = Encoding.UTF8.GetString(enumName.ToArray()); + + throw new JsonException($"Undefined mapping of '{stringValue}' to enum '{typeof(T).FullName}'"); } if (reader.TokenType is JsonTokenType.Number) @@ -50,7 +57,12 @@ reader.TokenType is JsonTokenType.String if (numericValue.HasValue) { - return ToEnum(numericValue.Value); + if (TryToEnum(numericValue.Value, out var enumValue)) + { + return enumValue; + } + + throw new JsonException($"Undefined mapping of '{numericValue}' to enum '{{enumFullName}}'"); } } @@ -59,6 +71,44 @@ reader.TokenType is JsonTokenType.String throw new JsonException($"Unable to deserialize {value}('{reader.TokenType}') into {typeof(T).Name}"); } + public override T ReadAsPropertyName( + ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options + ) + { + if ( + reader.TokenType is JsonTokenType.PropertyName + && DeserializationStrategy.HasFlag(EnumDeserializationStrategy.UseEnumName) + ) + { + var enumName = reader.ValueSpan; + + if (TryToEnum(enumName, out var enumValue)) + { + return enumValue; + } + } + + var value = Encoding.UTF8.GetString(reader.ValueSpan.ToArray()); + + if ( + reader.TokenType is JsonTokenType.PropertyName + && DeserializationStrategy.HasFlag(EnumDeserializationStrategy.UseBackingType) + ) + { + var numericValue = ParseAsNumber(value); + + if (numericValue.HasValue) + { + if (TryToEnum(numericValue.Value, out var enumValue)) + { + return enumValue; + } + } + } + + throw new JsonException($"Unable to deserialize {value}('{reader.TokenType}') into {typeof(T).Name}"); + } + private TBackingType? ReadAsNumber(ref Utf8JsonReader reader) => BackingTypeTypeCode switch { TypeCode.SByte => reader.GetSByte() is var numericValue ? Unsafe.As(ref numericValue) : null, @@ -72,6 +122,21 @@ reader.TokenType is JsonTokenType.String _ => throw new ArgumentOutOfRangeException(nameof(BackingTypeTypeCode), BackingTypeTypeCode, $"Unexpected TypeCode {BackingTypeTypeCode}") }; + private TBackingType? ParseAsNumber( + string value + ) => BackingTypeTypeCode switch + { + TypeCode.SByte => sbyte.TryParse(value, out var numericValue) ? Unsafe.As(ref numericValue) : null, + TypeCode.Byte => byte.TryParse(value, out var numericValue) ? Unsafe.As(ref numericValue) : null, + TypeCode.Int16 => short.TryParse(value, out var numericValue) ? Unsafe.As(ref numericValue) : null, + TypeCode.UInt16 => ushort.TryParse(value, out var numericValue) ? Unsafe.As(ref numericValue) : null, + TypeCode.Int32 => int.TryParse(value, out var numericValue) ? Unsafe.As(ref numericValue) : null, + TypeCode.UInt32 => uint.TryParse(value, out var numericValue) ? Unsafe.As(ref numericValue) : null, + TypeCode.Int64 => long.TryParse(value, out var numericValue) ? Unsafe.As(ref numericValue) : null, + TypeCode.UInt64 => ulong.TryParse(value, out var numericValue) ? Unsafe.As(ref numericValue) : null, + _ => throw new ArgumentOutOfRangeException(nameof(BackingTypeTypeCode), BackingTypeTypeCode, $"Unexpected TypeCode {BackingTypeTypeCode}") + }; + public override void Write(Utf8JsonWriter writer, T value, JsonSerializerOptions options) { if (SerializationStrategy is EnumSerializationStrategy.BackingType) @@ -88,6 +153,22 @@ public override void Write(Utf8JsonWriter writer, T value, JsonSerializerOptions } } + public override void WriteAsPropertyName(Utf8JsonWriter writer, T value, JsonSerializerOptions options) + { + if (SerializationStrategy is EnumSerializationStrategy.BackingType) + { + WriteAsPropertyNameAsBackingType(writer, value, options); + } + else if (SerializationStrategy is EnumSerializationStrategy.FirstEnumName) + { + WriteAsPropertyNameAsFirstEnumName(writer, value, options); + } + else + { + throw new ArgumentOutOfRangeException(nameof(SerializationStrategy), SerializationStrategy, "Unknown serialization strategy"); + } + } + private void WriteAsBackingType( Utf8JsonWriter writer, T value, @@ -128,6 +209,18 @@ JsonSerializerOptions options } } + private void WriteAsPropertyNameAsBackingType( + Utf8JsonWriter writer, + T value, + [SuppressMessage("ReSharper", "UnusedParameter.Local")] + JsonSerializerOptions options + ) + { + var numericValue = ToBackingType(value); + + writer.WritePropertyName($"{numericValue}"); + } + private void WriteAsFirstEnumName( Utf8JsonWriter writer, T value, @@ -139,4 +232,16 @@ JsonSerializerOptions options writer.WriteStringValue(enumValue); } + + private void WriteAsPropertyNameAsFirstEnumName( + Utf8JsonWriter writer, + T value, + [SuppressMessage("ReSharper", "UnusedParameter.Local")] + JsonSerializerOptions options + ) + { + var enumValue = ToFirstEnumName(value); + + writer.WritePropertyName(enumValue); + } } diff --git a/src/Aviationexam.GeneratedJsonConverters.SourceGenerator.Tests/EnumJsonConverterIncrementalGeneratorSnapshotTests.ProjectConfigurationWorks_UseBackingType_UseEnumName#EMyEnumEnumJsonConverter.g.verified.cs b/src/Aviationexam.GeneratedJsonConverters.SourceGenerator.Tests/EnumJsonConverterIncrementalGeneratorSnapshotTests.ProjectConfigurationWorks_UseBackingType_UseEnumName#EMyEnumEnumJsonConverter.g.verified.cs index 185d5e0..79dc9f3 100644 --- a/src/Aviationexam.GeneratedJsonConverters.SourceGenerator.Tests/EnumJsonConverterIncrementalGeneratorSnapshotTests.ProjectConfigurationWorks_UseBackingType_UseEnumName#EMyEnumEnumJsonConverter.g.verified.cs +++ b/src/Aviationexam.GeneratedJsonConverters.SourceGenerator.Tests/EnumJsonConverterIncrementalGeneratorSnapshotTests.ProjectConfigurationWorks_UseBackingType_UseEnumName#EMyEnumEnumJsonConverter.g.verified.cs @@ -11,32 +11,38 @@ internal class EMyEnumEnumJsonConverter : Aviationexam.GeneratedJsonConverters.E protected override Aviationexam.GeneratedJsonConverters.EnumSerializationStrategy SerializationStrategy => Aviationexam.GeneratedJsonConverters.EnumSerializationStrategy.BackingType; - protected override ApplicationNamespace.Contracts.EMyEnum ToEnum( - System.ReadOnlySpan enumName + protected override bool TryToEnum( + System.ReadOnlySpan enumName, out ApplicationNamespace.Contracts.EMyEnum value ) { if (System.MemoryExtensions.SequenceEqual(enumName, "C"u8)) { - return ApplicationNamespace.Contracts.EMyEnum.A; + value = ApplicationNamespace.Contracts.EMyEnum.A; + return true; } if (System.MemoryExtensions.SequenceEqual(enumName, "D"u8)) { - return ApplicationNamespace.Contracts.EMyEnum.B; + value = ApplicationNamespace.Contracts.EMyEnum.B; + return true; } - var stringValue = System.Text.Encoding.UTF8.GetString(enumName.ToArray()); - - throw new System.Text.Json.JsonException($"Undefined mapping of '{stringValue}' to enum 'ApplicationNamespace.Contracts.EMyEnum'"); + value = default(ApplicationNamespace.Contracts.EMyEnum); + return false; } - protected override ApplicationNamespace.Contracts.EMyEnum ToEnum( - System.Int32 numericValue - ) => numericValue switch + protected override bool TryToEnum( + System.Int32 numericValue, out ApplicationNamespace.Contracts.EMyEnum value + ) { - 0 => ApplicationNamespace.Contracts.EMyEnum.A, - 1 => ApplicationNamespace.Contracts.EMyEnum.B, - _ => throw new System.Text.Json.JsonException($"Undefined mapping of '{numericValue}' to enum 'ApplicationNamespace.Contracts.EMyEnum'"), - }; + (var tryValue, value) = numericValue switch + { + 0 => (true, ApplicationNamespace.Contracts.EMyEnum.A), + 1 => (true, ApplicationNamespace.Contracts.EMyEnum.B), + _ => (false, default(ApplicationNamespace.Contracts.EMyEnum)), + }; + + return tryValue; + } protected override System.Int32 ToBackingType( ApplicationNamespace.Contracts.EMyEnum value @@ -50,4 +56,4 @@ ApplicationNamespace.Contracts.EMyEnum value protected override System.ReadOnlySpan ToFirstEnumName( ApplicationNamespace.Contracts.EMyEnum value ) => throw new System.Text.Json.JsonException("Enum is not configured to support serialization to enum type"); -} \ No newline at end of file +} diff --git a/src/Aviationexam.GeneratedJsonConverters.SourceGenerator.Tests/EnumJsonConverterIncrementalGeneratorSnapshotTests.ProjectConfigurationWorks_UseBackingType_UseEnumName#EnumJsonConvertor.g.verified.cs b/src/Aviationexam.GeneratedJsonConverters.SourceGenerator.Tests/EnumJsonConverterIncrementalGeneratorSnapshotTests.ProjectConfigurationWorks_UseBackingType_UseEnumName#EnumJsonConvertor.g.verified.cs index 1fc6eaa..34d3193 100644 --- a/src/Aviationexam.GeneratedJsonConverters.SourceGenerator.Tests/EnumJsonConverterIncrementalGeneratorSnapshotTests.ProjectConfigurationWorks_UseBackingType_UseEnumName#EnumJsonConvertor.g.verified.cs +++ b/src/Aviationexam.GeneratedJsonConverters.SourceGenerator.Tests/EnumJsonConverterIncrementalGeneratorSnapshotTests.ProjectConfigurationWorks_UseBackingType_UseEnumName#EnumJsonConvertor.g.verified.cs @@ -22,9 +22,9 @@ internal abstract class EnumJsonConvertor : JsonConverter protected abstract EnumSerializationStrategy SerializationStrategy { get; } - protected abstract T ToEnum(ReadOnlySpan enumName); + protected abstract bool TryToEnum(ReadOnlySpan enumName, out T value); - protected abstract T ToEnum(TBackingType numericValue); + protected abstract bool TryToEnum(TBackingType numericValue, out T value); protected abstract TBackingType ToBackingType(T value); @@ -41,7 +41,14 @@ reader.TokenType is JsonTokenType.String { var enumName = reader.ValueSpan; - return ToEnum(enumName); + if (TryToEnum(enumName, out var enumValue)) + { + return enumValue; + } + + var stringValue = Encoding.UTF8.GetString(enumName.ToArray()); + + throw new JsonException($"Undefined mapping of '{stringValue}' to enum '{typeof(T).FullName}'"); } if (reader.TokenType is JsonTokenType.Number) @@ -50,7 +57,12 @@ reader.TokenType is JsonTokenType.String if (numericValue.HasValue) { - return ToEnum(numericValue.Value); + if (TryToEnum(numericValue.Value, out var enumValue)) + { + return enumValue; + } + + throw new JsonException($"Undefined mapping of '{numericValue}' to enum '{{enumFullName}}'"); } } @@ -59,6 +71,44 @@ reader.TokenType is JsonTokenType.String throw new JsonException($"Unable to deserialize {value}('{reader.TokenType}') into {typeof(T).Name}"); } + public override T ReadAsPropertyName( + ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options + ) + { + if ( + reader.TokenType is JsonTokenType.PropertyName + && DeserializationStrategy.HasFlag(EnumDeserializationStrategy.UseEnumName) + ) + { + var enumName = reader.ValueSpan; + + if (TryToEnum(enumName, out var enumValue)) + { + return enumValue; + } + } + + var value = Encoding.UTF8.GetString(reader.ValueSpan.ToArray()); + + if ( + reader.TokenType is JsonTokenType.PropertyName + && DeserializationStrategy.HasFlag(EnumDeserializationStrategy.UseBackingType) + ) + { + var numericValue = ParseAsNumber(value); + + if (numericValue.HasValue) + { + if (TryToEnum(numericValue.Value, out var enumValue)) + { + return enumValue; + } + } + } + + throw new JsonException($"Unable to deserialize {value}('{reader.TokenType}') into {typeof(T).Name}"); + } + private TBackingType? ReadAsNumber(ref Utf8JsonReader reader) => BackingTypeTypeCode switch { TypeCode.SByte => reader.GetSByte() is var numericValue ? Unsafe.As(ref numericValue) : null, @@ -72,6 +122,21 @@ reader.TokenType is JsonTokenType.String _ => throw new ArgumentOutOfRangeException(nameof(BackingTypeTypeCode), BackingTypeTypeCode, $"Unexpected TypeCode {BackingTypeTypeCode}") }; + private TBackingType? ParseAsNumber( + string value + ) => BackingTypeTypeCode switch + { + TypeCode.SByte => sbyte.TryParse(value, out var numericValue) ? Unsafe.As(ref numericValue) : null, + TypeCode.Byte => byte.TryParse(value, out var numericValue) ? Unsafe.As(ref numericValue) : null, + TypeCode.Int16 => short.TryParse(value, out var numericValue) ? Unsafe.As(ref numericValue) : null, + TypeCode.UInt16 => ushort.TryParse(value, out var numericValue) ? Unsafe.As(ref numericValue) : null, + TypeCode.Int32 => int.TryParse(value, out var numericValue) ? Unsafe.As(ref numericValue) : null, + TypeCode.UInt32 => uint.TryParse(value, out var numericValue) ? Unsafe.As(ref numericValue) : null, + TypeCode.Int64 => long.TryParse(value, out var numericValue) ? Unsafe.As(ref numericValue) : null, + TypeCode.UInt64 => ulong.TryParse(value, out var numericValue) ? Unsafe.As(ref numericValue) : null, + _ => throw new ArgumentOutOfRangeException(nameof(BackingTypeTypeCode), BackingTypeTypeCode, $"Unexpected TypeCode {BackingTypeTypeCode}") + }; + public override void Write(Utf8JsonWriter writer, T value, JsonSerializerOptions options) { if (SerializationStrategy is EnumSerializationStrategy.BackingType) @@ -88,6 +153,22 @@ public override void Write(Utf8JsonWriter writer, T value, JsonSerializerOptions } } + public override void WriteAsPropertyName(Utf8JsonWriter writer, T value, JsonSerializerOptions options) + { + if (SerializationStrategy is EnumSerializationStrategy.BackingType) + { + WriteAsPropertyNameAsBackingType(writer, value, options); + } + else if (SerializationStrategy is EnumSerializationStrategy.FirstEnumName) + { + WriteAsPropertyNameAsFirstEnumName(writer, value, options); + } + else + { + throw new ArgumentOutOfRangeException(nameof(SerializationStrategy), SerializationStrategy, "Unknown serialization strategy"); + } + } + private void WriteAsBackingType( Utf8JsonWriter writer, T value, @@ -128,6 +209,18 @@ JsonSerializerOptions options } } + private void WriteAsPropertyNameAsBackingType( + Utf8JsonWriter writer, + T value, + [SuppressMessage("ReSharper", "UnusedParameter.Local")] + JsonSerializerOptions options + ) + { + var numericValue = ToBackingType(value); + + writer.WritePropertyName($"{numericValue}"); + } + private void WriteAsFirstEnumName( Utf8JsonWriter writer, T value, @@ -139,4 +232,16 @@ JsonSerializerOptions options writer.WriteStringValue(enumValue); } + + private void WriteAsPropertyNameAsFirstEnumName( + Utf8JsonWriter writer, + T value, + [SuppressMessage("ReSharper", "UnusedParameter.Local")] + JsonSerializerOptions options + ) + { + var enumValue = ToFirstEnumName(value); + + writer.WritePropertyName(enumValue); + } } diff --git a/src/Aviationexam.GeneratedJsonConverters.SourceGenerator.Tests/EnumJsonConverterIncrementalGeneratorSnapshotTests.ProjectConfigurationWorks_UseEnumName#EMyEnumEnumJsonConverter.g.verified.cs b/src/Aviationexam.GeneratedJsonConverters.SourceGenerator.Tests/EnumJsonConverterIncrementalGeneratorSnapshotTests.ProjectConfigurationWorks_UseEnumName#EMyEnumEnumJsonConverter.g.verified.cs index 268d7e1..99a025e 100644 --- a/src/Aviationexam.GeneratedJsonConverters.SourceGenerator.Tests/EnumJsonConverterIncrementalGeneratorSnapshotTests.ProjectConfigurationWorks_UseEnumName#EMyEnumEnumJsonConverter.g.verified.cs +++ b/src/Aviationexam.GeneratedJsonConverters.SourceGenerator.Tests/EnumJsonConverterIncrementalGeneratorSnapshotTests.ProjectConfigurationWorks_UseEnumName#EMyEnumEnumJsonConverter.g.verified.cs @@ -11,26 +11,27 @@ internal class EMyEnumEnumJsonConverter : Aviationexam.GeneratedJsonConverters.E protected override Aviationexam.GeneratedJsonConverters.EnumSerializationStrategy SerializationStrategy => Aviationexam.GeneratedJsonConverters.EnumSerializationStrategy.FirstEnumName; - protected override ApplicationNamespace.Contracts.EMyEnum ToEnum( - System.ReadOnlySpan enumName + protected override bool TryToEnum( + System.ReadOnlySpan enumName, out ApplicationNamespace.Contracts.EMyEnum value ) { if (System.MemoryExtensions.SequenceEqual(enumName, "C"u8)) { - return ApplicationNamespace.Contracts.EMyEnum.A; + value = ApplicationNamespace.Contracts.EMyEnum.A; + return true; } if (System.MemoryExtensions.SequenceEqual(enumName, "D"u8)) { - return ApplicationNamespace.Contracts.EMyEnum.B; + value = ApplicationNamespace.Contracts.EMyEnum.B; + return true; } - var stringValue = System.Text.Encoding.UTF8.GetString(enumName.ToArray()); - - throw new System.Text.Json.JsonException($"Undefined mapping of '{stringValue}' to enum 'ApplicationNamespace.Contracts.EMyEnum'"); + value = default(ApplicationNamespace.Contracts.EMyEnum); + return false; } - protected override ApplicationNamespace.Contracts.EMyEnum ToEnum( - System.Int32 numericValue + protected override bool TryToEnum( + System.Int32 numericValue, out ApplicationNamespace.Contracts.EMyEnum value ) => throw new System.Text.Json.JsonException("Enum is not configured to support deserialization from backing type"); protected override System.Int32 ToBackingType( @@ -45,4 +46,4 @@ ApplicationNamespace.Contracts.EMyEnum value ApplicationNamespace.Contracts.EMyEnum.B => "D"u8, _ => throw new System.Text.Json.JsonException($"Undefined mapping of '{value}' from enum 'ApplicationNamespace.Contracts.EMyEnum'"), }; -} \ No newline at end of file +} diff --git a/src/Aviationexam.GeneratedJsonConverters.SourceGenerator.Tests/EnumJsonConverterIncrementalGeneratorSnapshotTests.ProjectConfigurationWorks_UseEnumName#EnumJsonConvertor.g.verified.cs b/src/Aviationexam.GeneratedJsonConverters.SourceGenerator.Tests/EnumJsonConverterIncrementalGeneratorSnapshotTests.ProjectConfigurationWorks_UseEnumName#EnumJsonConvertor.g.verified.cs index 1fc6eaa..34d3193 100644 --- a/src/Aviationexam.GeneratedJsonConverters.SourceGenerator.Tests/EnumJsonConverterIncrementalGeneratorSnapshotTests.ProjectConfigurationWorks_UseEnumName#EnumJsonConvertor.g.verified.cs +++ b/src/Aviationexam.GeneratedJsonConverters.SourceGenerator.Tests/EnumJsonConverterIncrementalGeneratorSnapshotTests.ProjectConfigurationWorks_UseEnumName#EnumJsonConvertor.g.verified.cs @@ -22,9 +22,9 @@ internal abstract class EnumJsonConvertor : JsonConverter protected abstract EnumSerializationStrategy SerializationStrategy { get; } - protected abstract T ToEnum(ReadOnlySpan enumName); + protected abstract bool TryToEnum(ReadOnlySpan enumName, out T value); - protected abstract T ToEnum(TBackingType numericValue); + protected abstract bool TryToEnum(TBackingType numericValue, out T value); protected abstract TBackingType ToBackingType(T value); @@ -41,7 +41,14 @@ reader.TokenType is JsonTokenType.String { var enumName = reader.ValueSpan; - return ToEnum(enumName); + if (TryToEnum(enumName, out var enumValue)) + { + return enumValue; + } + + var stringValue = Encoding.UTF8.GetString(enumName.ToArray()); + + throw new JsonException($"Undefined mapping of '{stringValue}' to enum '{typeof(T).FullName}'"); } if (reader.TokenType is JsonTokenType.Number) @@ -50,7 +57,12 @@ reader.TokenType is JsonTokenType.String if (numericValue.HasValue) { - return ToEnum(numericValue.Value); + if (TryToEnum(numericValue.Value, out var enumValue)) + { + return enumValue; + } + + throw new JsonException($"Undefined mapping of '{numericValue}' to enum '{{enumFullName}}'"); } } @@ -59,6 +71,44 @@ reader.TokenType is JsonTokenType.String throw new JsonException($"Unable to deserialize {value}('{reader.TokenType}') into {typeof(T).Name}"); } + public override T ReadAsPropertyName( + ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options + ) + { + if ( + reader.TokenType is JsonTokenType.PropertyName + && DeserializationStrategy.HasFlag(EnumDeserializationStrategy.UseEnumName) + ) + { + var enumName = reader.ValueSpan; + + if (TryToEnum(enumName, out var enumValue)) + { + return enumValue; + } + } + + var value = Encoding.UTF8.GetString(reader.ValueSpan.ToArray()); + + if ( + reader.TokenType is JsonTokenType.PropertyName + && DeserializationStrategy.HasFlag(EnumDeserializationStrategy.UseBackingType) + ) + { + var numericValue = ParseAsNumber(value); + + if (numericValue.HasValue) + { + if (TryToEnum(numericValue.Value, out var enumValue)) + { + return enumValue; + } + } + } + + throw new JsonException($"Unable to deserialize {value}('{reader.TokenType}') into {typeof(T).Name}"); + } + private TBackingType? ReadAsNumber(ref Utf8JsonReader reader) => BackingTypeTypeCode switch { TypeCode.SByte => reader.GetSByte() is var numericValue ? Unsafe.As(ref numericValue) : null, @@ -72,6 +122,21 @@ reader.TokenType is JsonTokenType.String _ => throw new ArgumentOutOfRangeException(nameof(BackingTypeTypeCode), BackingTypeTypeCode, $"Unexpected TypeCode {BackingTypeTypeCode}") }; + private TBackingType? ParseAsNumber( + string value + ) => BackingTypeTypeCode switch + { + TypeCode.SByte => sbyte.TryParse(value, out var numericValue) ? Unsafe.As(ref numericValue) : null, + TypeCode.Byte => byte.TryParse(value, out var numericValue) ? Unsafe.As(ref numericValue) : null, + TypeCode.Int16 => short.TryParse(value, out var numericValue) ? Unsafe.As(ref numericValue) : null, + TypeCode.UInt16 => ushort.TryParse(value, out var numericValue) ? Unsafe.As(ref numericValue) : null, + TypeCode.Int32 => int.TryParse(value, out var numericValue) ? Unsafe.As(ref numericValue) : null, + TypeCode.UInt32 => uint.TryParse(value, out var numericValue) ? Unsafe.As(ref numericValue) : null, + TypeCode.Int64 => long.TryParse(value, out var numericValue) ? Unsafe.As(ref numericValue) : null, + TypeCode.UInt64 => ulong.TryParse(value, out var numericValue) ? Unsafe.As(ref numericValue) : null, + _ => throw new ArgumentOutOfRangeException(nameof(BackingTypeTypeCode), BackingTypeTypeCode, $"Unexpected TypeCode {BackingTypeTypeCode}") + }; + public override void Write(Utf8JsonWriter writer, T value, JsonSerializerOptions options) { if (SerializationStrategy is EnumSerializationStrategy.BackingType) @@ -88,6 +153,22 @@ public override void Write(Utf8JsonWriter writer, T value, JsonSerializerOptions } } + public override void WriteAsPropertyName(Utf8JsonWriter writer, T value, JsonSerializerOptions options) + { + if (SerializationStrategy is EnumSerializationStrategy.BackingType) + { + WriteAsPropertyNameAsBackingType(writer, value, options); + } + else if (SerializationStrategy is EnumSerializationStrategy.FirstEnumName) + { + WriteAsPropertyNameAsFirstEnumName(writer, value, options); + } + else + { + throw new ArgumentOutOfRangeException(nameof(SerializationStrategy), SerializationStrategy, "Unknown serialization strategy"); + } + } + private void WriteAsBackingType( Utf8JsonWriter writer, T value, @@ -128,6 +209,18 @@ JsonSerializerOptions options } } + private void WriteAsPropertyNameAsBackingType( + Utf8JsonWriter writer, + T value, + [SuppressMessage("ReSharper", "UnusedParameter.Local")] + JsonSerializerOptions options + ) + { + var numericValue = ToBackingType(value); + + writer.WritePropertyName($"{numericValue}"); + } + private void WriteAsFirstEnumName( Utf8JsonWriter writer, T value, @@ -139,4 +232,16 @@ JsonSerializerOptions options writer.WriteStringValue(enumValue); } + + private void WriteAsPropertyNameAsFirstEnumName( + Utf8JsonWriter writer, + T value, + [SuppressMessage("ReSharper", "UnusedParameter.Local")] + JsonSerializerOptions options + ) + { + var enumValue = ToFirstEnumName(value); + + writer.WritePropertyName(enumValue); + } } diff --git a/src/Aviationexam.GeneratedJsonConverters.SourceGenerator.Tests/EnumJsonConverterIncrementalGeneratorSnapshotTests.SimpleWorks#EMyEnumEnumJsonConverter.g.verified.cs b/src/Aviationexam.GeneratedJsonConverters.SourceGenerator.Tests/EnumJsonConverterIncrementalGeneratorSnapshotTests.SimpleWorks#EMyEnumEnumJsonConverter.g.verified.cs index 762601a..0bd13ee 100644 --- a/src/Aviationexam.GeneratedJsonConverters.SourceGenerator.Tests/EnumJsonConverterIncrementalGeneratorSnapshotTests.SimpleWorks#EMyEnumEnumJsonConverter.g.verified.cs +++ b/src/Aviationexam.GeneratedJsonConverters.SourceGenerator.Tests/EnumJsonConverterIncrementalGeneratorSnapshotTests.SimpleWorks#EMyEnumEnumJsonConverter.g.verified.cs @@ -11,26 +11,27 @@ internal class EMyEnumEnumJsonConverter : Aviationexam.GeneratedJsonConverters.E protected override Aviationexam.GeneratedJsonConverters.EnumSerializationStrategy SerializationStrategy => Aviationexam.GeneratedJsonConverters.EnumSerializationStrategy.FirstEnumName; - protected override ApplicationNamespace.Contracts.EMyEnum ToEnum( - System.ReadOnlySpan enumName + protected override bool TryToEnum( + System.ReadOnlySpan enumName, out ApplicationNamespace.Contracts.EMyEnum value ) { if (System.MemoryExtensions.SequenceEqual(enumName, "A"u8)) { - return ApplicationNamespace.Contracts.EMyEnum.A; + value = ApplicationNamespace.Contracts.EMyEnum.A; + return true; } if (System.MemoryExtensions.SequenceEqual(enumName, "B"u8)) { - return ApplicationNamespace.Contracts.EMyEnum.B; + value = ApplicationNamespace.Contracts.EMyEnum.B; + return true; } - var stringValue = System.Text.Encoding.UTF8.GetString(enumName.ToArray()); - - throw new System.Text.Json.JsonException($"Undefined mapping of '{stringValue}' to enum 'ApplicationNamespace.Contracts.EMyEnum'"); + value = default(ApplicationNamespace.Contracts.EMyEnum); + return false; } - protected override ApplicationNamespace.Contracts.EMyEnum ToEnum( - System.Int32 numericValue + protected override bool TryToEnum( + System.Int32 numericValue, out ApplicationNamespace.Contracts.EMyEnum value ) => throw new System.Text.Json.JsonException("Enum is not configured to support deserialization from backing type"); protected override System.Int32 ToBackingType( diff --git a/src/Aviationexam.GeneratedJsonConverters.SourceGenerator.Tests/EnumJsonConverterIncrementalGeneratorSnapshotTests.SimpleWorks#EnumJsonConvertor.g.verified.cs b/src/Aviationexam.GeneratedJsonConverters.SourceGenerator.Tests/EnumJsonConverterIncrementalGeneratorSnapshotTests.SimpleWorks#EnumJsonConvertor.g.verified.cs index 1fc6eaa..34d3193 100644 --- a/src/Aviationexam.GeneratedJsonConverters.SourceGenerator.Tests/EnumJsonConverterIncrementalGeneratorSnapshotTests.SimpleWorks#EnumJsonConvertor.g.verified.cs +++ b/src/Aviationexam.GeneratedJsonConverters.SourceGenerator.Tests/EnumJsonConverterIncrementalGeneratorSnapshotTests.SimpleWorks#EnumJsonConvertor.g.verified.cs @@ -22,9 +22,9 @@ internal abstract class EnumJsonConvertor : JsonConverter protected abstract EnumSerializationStrategy SerializationStrategy { get; } - protected abstract T ToEnum(ReadOnlySpan enumName); + protected abstract bool TryToEnum(ReadOnlySpan enumName, out T value); - protected abstract T ToEnum(TBackingType numericValue); + protected abstract bool TryToEnum(TBackingType numericValue, out T value); protected abstract TBackingType ToBackingType(T value); @@ -41,7 +41,14 @@ reader.TokenType is JsonTokenType.String { var enumName = reader.ValueSpan; - return ToEnum(enumName); + if (TryToEnum(enumName, out var enumValue)) + { + return enumValue; + } + + var stringValue = Encoding.UTF8.GetString(enumName.ToArray()); + + throw new JsonException($"Undefined mapping of '{stringValue}' to enum '{typeof(T).FullName}'"); } if (reader.TokenType is JsonTokenType.Number) @@ -50,7 +57,12 @@ reader.TokenType is JsonTokenType.String if (numericValue.HasValue) { - return ToEnum(numericValue.Value); + if (TryToEnum(numericValue.Value, out var enumValue)) + { + return enumValue; + } + + throw new JsonException($"Undefined mapping of '{numericValue}' to enum '{{enumFullName}}'"); } } @@ -59,6 +71,44 @@ reader.TokenType is JsonTokenType.String throw new JsonException($"Unable to deserialize {value}('{reader.TokenType}') into {typeof(T).Name}"); } + public override T ReadAsPropertyName( + ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options + ) + { + if ( + reader.TokenType is JsonTokenType.PropertyName + && DeserializationStrategy.HasFlag(EnumDeserializationStrategy.UseEnumName) + ) + { + var enumName = reader.ValueSpan; + + if (TryToEnum(enumName, out var enumValue)) + { + return enumValue; + } + } + + var value = Encoding.UTF8.GetString(reader.ValueSpan.ToArray()); + + if ( + reader.TokenType is JsonTokenType.PropertyName + && DeserializationStrategy.HasFlag(EnumDeserializationStrategy.UseBackingType) + ) + { + var numericValue = ParseAsNumber(value); + + if (numericValue.HasValue) + { + if (TryToEnum(numericValue.Value, out var enumValue)) + { + return enumValue; + } + } + } + + throw new JsonException($"Unable to deserialize {value}('{reader.TokenType}') into {typeof(T).Name}"); + } + private TBackingType? ReadAsNumber(ref Utf8JsonReader reader) => BackingTypeTypeCode switch { TypeCode.SByte => reader.GetSByte() is var numericValue ? Unsafe.As(ref numericValue) : null, @@ -72,6 +122,21 @@ reader.TokenType is JsonTokenType.String _ => throw new ArgumentOutOfRangeException(nameof(BackingTypeTypeCode), BackingTypeTypeCode, $"Unexpected TypeCode {BackingTypeTypeCode}") }; + private TBackingType? ParseAsNumber( + string value + ) => BackingTypeTypeCode switch + { + TypeCode.SByte => sbyte.TryParse(value, out var numericValue) ? Unsafe.As(ref numericValue) : null, + TypeCode.Byte => byte.TryParse(value, out var numericValue) ? Unsafe.As(ref numericValue) : null, + TypeCode.Int16 => short.TryParse(value, out var numericValue) ? Unsafe.As(ref numericValue) : null, + TypeCode.UInt16 => ushort.TryParse(value, out var numericValue) ? Unsafe.As(ref numericValue) : null, + TypeCode.Int32 => int.TryParse(value, out var numericValue) ? Unsafe.As(ref numericValue) : null, + TypeCode.UInt32 => uint.TryParse(value, out var numericValue) ? Unsafe.As(ref numericValue) : null, + TypeCode.Int64 => long.TryParse(value, out var numericValue) ? Unsafe.As(ref numericValue) : null, + TypeCode.UInt64 => ulong.TryParse(value, out var numericValue) ? Unsafe.As(ref numericValue) : null, + _ => throw new ArgumentOutOfRangeException(nameof(BackingTypeTypeCode), BackingTypeTypeCode, $"Unexpected TypeCode {BackingTypeTypeCode}") + }; + public override void Write(Utf8JsonWriter writer, T value, JsonSerializerOptions options) { if (SerializationStrategy is EnumSerializationStrategy.BackingType) @@ -88,6 +153,22 @@ public override void Write(Utf8JsonWriter writer, T value, JsonSerializerOptions } } + public override void WriteAsPropertyName(Utf8JsonWriter writer, T value, JsonSerializerOptions options) + { + if (SerializationStrategy is EnumSerializationStrategy.BackingType) + { + WriteAsPropertyNameAsBackingType(writer, value, options); + } + else if (SerializationStrategy is EnumSerializationStrategy.FirstEnumName) + { + WriteAsPropertyNameAsFirstEnumName(writer, value, options); + } + else + { + throw new ArgumentOutOfRangeException(nameof(SerializationStrategy), SerializationStrategy, "Unknown serialization strategy"); + } + } + private void WriteAsBackingType( Utf8JsonWriter writer, T value, @@ -128,6 +209,18 @@ JsonSerializerOptions options } } + private void WriteAsPropertyNameAsBackingType( + Utf8JsonWriter writer, + T value, + [SuppressMessage("ReSharper", "UnusedParameter.Local")] + JsonSerializerOptions options + ) + { + var numericValue = ToBackingType(value); + + writer.WritePropertyName($"{numericValue}"); + } + private void WriteAsFirstEnumName( Utf8JsonWriter writer, T value, @@ -139,4 +232,16 @@ JsonSerializerOptions options writer.WriteStringValue(enumValue); } + + private void WriteAsPropertyNameAsFirstEnumName( + Utf8JsonWriter writer, + T value, + [SuppressMessage("ReSharper", "UnusedParameter.Local")] + JsonSerializerOptions options + ) + { + var enumValue = ToFirstEnumName(value); + + writer.WritePropertyName(enumValue); + } } diff --git a/src/Aviationexam.GeneratedJsonConverters.SourceGenerator/EnumJsonConvertor.cs b/src/Aviationexam.GeneratedJsonConverters.SourceGenerator/EnumJsonConvertor.cs index e6bc4a4..9c61da4 100644 --- a/src/Aviationexam.GeneratedJsonConverters.SourceGenerator/EnumJsonConvertor.cs +++ b/src/Aviationexam.GeneratedJsonConverters.SourceGenerator/EnumJsonConvertor.cs @@ -21,9 +21,9 @@ internal abstract class EnumJsonConvertor : JsonConverter protected abstract EnumSerializationStrategy SerializationStrategy { get; } - protected abstract T ToEnum(ReadOnlySpan enumName); + protected abstract bool TryToEnum(ReadOnlySpan enumName, out T value); - protected abstract T ToEnum(TBackingType numericValue); + protected abstract bool TryToEnum(TBackingType numericValue, out T value); protected abstract TBackingType ToBackingType(T value); @@ -40,7 +40,14 @@ reader.TokenType is JsonTokenType.String { var enumName = reader.ValueSpan; - return ToEnum(enumName); + if (TryToEnum(enumName, out var enumValue)) + { + return enumValue; + } + + var stringValue = Encoding.UTF8.GetString(enumName.ToArray()); + + throw new JsonException($"Undefined mapping of '{stringValue}' to enum '{typeof(T).FullName}'"); } if (reader.TokenType is JsonTokenType.Number) @@ -49,7 +56,12 @@ reader.TokenType is JsonTokenType.String if (numericValue.HasValue) { - return ToEnum(numericValue.Value); + if (TryToEnum(numericValue.Value, out var enumValue)) + { + return enumValue; + } + + throw new JsonException($"Undefined mapping of '{numericValue}' to enum '{{enumFullName}}'"); } } @@ -58,6 +70,44 @@ reader.TokenType is JsonTokenType.String throw new JsonException($"Unable to deserialize {value}('{reader.TokenType}') into {typeof(T).Name}"); } + public override T ReadAsPropertyName( + ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options + ) + { + if ( + reader.TokenType is JsonTokenType.PropertyName + && DeserializationStrategy.HasFlag(EnumDeserializationStrategy.UseEnumName) + ) + { + var enumName = reader.ValueSpan; + + if (TryToEnum(enumName, out var enumValue)) + { + return enumValue; + } + } + + var value = Encoding.UTF8.GetString(reader.ValueSpan.ToArray()); + + if ( + reader.TokenType is JsonTokenType.PropertyName + && DeserializationStrategy.HasFlag(EnumDeserializationStrategy.UseBackingType) + ) + { + var numericValue = ParseAsNumber(value); + + if (numericValue.HasValue) + { + if (TryToEnum(numericValue.Value, out var enumValue)) + { + return enumValue; + } + } + } + + throw new JsonException($"Unable to deserialize {value}('{reader.TokenType}') into {typeof(T).Name}"); + } + private TBackingType? ReadAsNumber(ref Utf8JsonReader reader) => BackingTypeTypeCode switch { TypeCode.SByte => reader.GetSByte() is var numericValue ? Unsafe.As(ref numericValue) : null, @@ -71,6 +121,21 @@ reader.TokenType is JsonTokenType.String _ => throw new ArgumentOutOfRangeException(nameof(BackingTypeTypeCode), BackingTypeTypeCode, $"Unexpected TypeCode {BackingTypeTypeCode}") }; + private TBackingType? ParseAsNumber( + string value + ) => BackingTypeTypeCode switch + { + TypeCode.SByte => sbyte.TryParse(value, out var numericValue) ? Unsafe.As(ref numericValue) : null, + TypeCode.Byte => byte.TryParse(value, out var numericValue) ? Unsafe.As(ref numericValue) : null, + TypeCode.Int16 => short.TryParse(value, out var numericValue) ? Unsafe.As(ref numericValue) : null, + TypeCode.UInt16 => ushort.TryParse(value, out var numericValue) ? Unsafe.As(ref numericValue) : null, + TypeCode.Int32 => int.TryParse(value, out var numericValue) ? Unsafe.As(ref numericValue) : null, + TypeCode.UInt32 => uint.TryParse(value, out var numericValue) ? Unsafe.As(ref numericValue) : null, + TypeCode.Int64 => long.TryParse(value, out var numericValue) ? Unsafe.As(ref numericValue) : null, + TypeCode.UInt64 => ulong.TryParse(value, out var numericValue) ? Unsafe.As(ref numericValue) : null, + _ => throw new ArgumentOutOfRangeException(nameof(BackingTypeTypeCode), BackingTypeTypeCode, $"Unexpected TypeCode {BackingTypeTypeCode}") + }; + public override void Write(Utf8JsonWriter writer, T value, JsonSerializerOptions options) { if (SerializationStrategy is EnumSerializationStrategy.BackingType) @@ -87,6 +152,22 @@ public override void Write(Utf8JsonWriter writer, T value, JsonSerializerOptions } } + public override void WriteAsPropertyName(Utf8JsonWriter writer, T value, JsonSerializerOptions options) + { + if (SerializationStrategy is EnumSerializationStrategy.BackingType) + { + WriteAsPropertyNameAsBackingType(writer, value, options); + } + else if (SerializationStrategy is EnumSerializationStrategy.FirstEnumName) + { + WriteAsPropertyNameAsFirstEnumName(writer, value, options); + } + else + { + throw new ArgumentOutOfRangeException(nameof(SerializationStrategy), SerializationStrategy, "Unknown serialization strategy"); + } + } + private void WriteAsBackingType( Utf8JsonWriter writer, T value, @@ -127,6 +208,18 @@ JsonSerializerOptions options } } + private void WriteAsPropertyNameAsBackingType( + Utf8JsonWriter writer, + T value, + [SuppressMessage("ReSharper", "UnusedParameter.Local")] + JsonSerializerOptions options + ) + { + var numericValue = ToBackingType(value); + + writer.WritePropertyName($"{numericValue}"); + } + private void WriteAsFirstEnumName( Utf8JsonWriter writer, T value, @@ -138,4 +231,16 @@ JsonSerializerOptions options writer.WriteStringValue(enumValue); } + + private void WriteAsPropertyNameAsFirstEnumName( + Utf8JsonWriter writer, + T value, + [SuppressMessage("ReSharper", "UnusedParameter.Local")] + JsonSerializerOptions options + ) + { + var enumValue = ToFirstEnumName(value); + + writer.WritePropertyName(enumValue); + } } diff --git a/src/Aviationexam.GeneratedJsonConverters.SourceGenerator/Generators/EnumJsonConverterGenerator.cs b/src/Aviationexam.GeneratedJsonConverters.SourceGenerator/Generators/EnumJsonConverterGenerator.cs index 967934e..6432a51 100644 --- a/src/Aviationexam.GeneratedJsonConverters.SourceGenerator/Generators/EnumJsonConverterGenerator.cs +++ b/src/Aviationexam.GeneratedJsonConverters.SourceGenerator/Generators/EnumJsonConverterGenerator.cs @@ -129,12 +129,12 @@ internal class {{converterName}} : Aviationexam.GeneratedJsonConverters.EnumJson protected override Aviationexam.GeneratedJsonConverters.EnumSerializationStrategy SerializationStrategy => Aviationexam.GeneratedJsonConverters.EnumSerializationStrategy.{{serializationStrategy}}; - protected override {{fullName}} ToEnum( - System.ReadOnlySpan enumName + protected override bool TryToEnum( + System.ReadOnlySpan enumName, out {{fullName}} value ){{toEnumFromString}} - protected override {{fullName}} ToEnum( - {{backingType}} numericValue + protected override bool TryToEnum( + {{backingType}} numericValue, out {{fullName}} value ){{toEnumFromBackingType}} protected override {{backingType}} ToBackingType( @@ -180,35 +180,40 @@ IDictionary backingTypeDeserialization stringBuilder.Append(MethodPrefix); stringBuilder.Append(Indention); stringBuilder.Append(Indention); - stringBuilder.Append("return "); + stringBuilder.Append("value = "); stringBuilder.Append(enumFullName); stringBuilder.Append("."); stringBuilder.Append(mapping.Value); stringBuilder.AppendLine(";"); + stringBuilder.Append(MethodPrefix); + stringBuilder.Append(Indention); + stringBuilder.Append(Indention); + stringBuilder.AppendLine("return true;"); + stringBuilder.Append(MethodPrefix); stringBuilder.Append(Indention); stringBuilder.AppendLine("}"); } stringBuilder.AppendLine(); + stringBuilder.Append(MethodPrefix); stringBuilder.Append(Indention); stringBuilder.AppendLine( // language=cs $""" - var stringValue = System.Text.Encoding.UTF8.GetString({propertyName}.ToArray()); - """ + value = default({enumFullName}); + """ ); - stringBuilder.AppendLine(); stringBuilder.Append(MethodPrefix); stringBuilder.Append(Indention); stringBuilder.AppendLine( // language=cs - $$""" - throw new System.Text.Json.JsonException($"Undefined mapping of '{stringValue}' to enum '{{enumFullName}}'"); - """ + """ + return false; + """ ); stringBuilder.Append(MethodPrefix); @@ -230,34 +235,48 @@ IDictionary backingTypeDeserialization { var stringBuilder = new StringBuilder(); - stringBuilder.AppendLine(" => numericValue switch"); + stringBuilder.AppendLine(); + stringBuilder.Append(MethodPrefix); + stringBuilder.AppendLine("{"); + stringBuilder.Append(MethodPrefix); + stringBuilder.Append(Indention); + stringBuilder.AppendLine("(var tryValue, value) = numericValue switch"); + + stringBuilder.Append(MethodPrefix); + stringBuilder.Append(Indention); stringBuilder.AppendLine("{"); foreach (var mapping in backingTypeDeserialization) { stringBuilder.Append(MethodPrefix); stringBuilder.Append(Indention); + stringBuilder.Append(Indention); stringBuilder.Append(mapping.Key); - stringBuilder.Append(" => "); + stringBuilder.Append(" => (true, "); stringBuilder.Append(enumFullName); stringBuilder.Append("."); stringBuilder.Append(mapping.Value); - stringBuilder.AppendLine(","); + stringBuilder.AppendLine("),"); } stringBuilder.Append(MethodPrefix); stringBuilder.Append(Indention); - stringBuilder.Append("_ => "); - stringBuilder.AppendLine( - // language=cs - $$""" - throw new System.Text.Json.JsonException($"Undefined mapping of '{numericValue}' to enum '{{enumFullName}}'"), - """ - ); + stringBuilder.Append(Indention); + stringBuilder.AppendLine($"_ => (false, default({enumFullName})),"); stringBuilder.Append(MethodPrefix); - stringBuilder.Append("};"); + stringBuilder.Append(Indention); + stringBuilder.AppendLine("};"); + + stringBuilder.AppendLine(); + + stringBuilder.Append(MethodPrefix); + stringBuilder.Append(Indention); + stringBuilder.AppendLine("return tryValue;"); + + stringBuilder.Append(MethodPrefix); + stringBuilder.Append("}"); return stringBuilder.ToString(); }