Skip to content

Commit c29468f

Browse files
committed
fix: serialize and deserialize enum members correctly
1 parent 8f5f3f9 commit c29468f

2 files changed

Lines changed: 92 additions & 5 deletions

File tree

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,15 @@
1-
using System.Runtime.Serialization;
2-
using System.Text.Json.Serialization;
1+
using System.Text.Json.Serialization;
2+
using WebApi.Util;
33

44
#pragma warning disable CS1591
55

66
namespace WebApi.Models;
77

8-
[JsonConverter(typeof(JsonStringEnumConverter))]
8+
[JsonConverter(typeof(JsonEnumConverter<SerializableLinkType>))]
99
public enum SerializableLinkType
1010
{
11-
[EnumMember(Value = "mirror")]
11+
[JsonEnumValue("mirror")]
1212
Mirror = 1,
13-
[EnumMember(Value = "download")]
13+
[JsonEnumValue("download")]
1414
Download = 2
1515
}

WebApi/Util/JsonEnumConverter.cs

Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
using System.Reflection;
2+
using System.Runtime.Serialization;
3+
using System.Text.Json;
4+
using System.Text.Json.Serialization;
5+
using FruityFoundation.Base.Structures;
6+
7+
namespace WebApi.Util;
8+
9+
/// <summary>
10+
/// A serializable and deserializable value for an enum value
11+
/// </summary>
12+
[AttributeUsage(AttributeTargets.Field, AllowMultiple = false, Inherited = true)]
13+
public class JsonEnumValueAttribute : Attribute
14+
{
15+
/// <summary>
16+
/// The string value.
17+
/// </summary>
18+
public string Value { get; }
19+
/// <summary>
20+
/// The comparison mode to use if performing string comparisons against <see cref="Value"/>.
21+
/// </summary>
22+
public StringComparison ComparisonMode { get; }
23+
24+
/// <summary>
25+
/// A serializable and deserializable value for an enum value
26+
/// </summary>
27+
/// <param name="value">The string value of this member</param>
28+
/// <param name="comparisonMode">The comparison mode to use.</param>
29+
public JsonEnumValueAttribute(string value, StringComparison comparisonMode = StringComparison.OrdinalIgnoreCase)
30+
{
31+
Value = value;
32+
ComparisonMode = comparisonMode;
33+
}
34+
}
35+
36+
/// <summary>
37+
/// Serialize and deserialize enums using string members
38+
/// </summary>
39+
public class JsonEnumConverter<T> : JsonConverter<T> where T : struct, Enum
40+
{
41+
/// <inheritdoc />
42+
public override T Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
43+
{
44+
var jsonValue = reader.GetString();
45+
46+
if (jsonValue is null)
47+
throw new SerializationException("value was null");
48+
49+
if (!FindEnumByJsonValue(jsonValue).Try(out var enumValue))
50+
throw new SerializationException($"Unable to map value to type ({typeof(T).FullName}): {jsonValue}");
51+
52+
return enumValue;
53+
}
54+
55+
/// <inheritdoc />
56+
public override void Write(Utf8JsonWriter writer, T value, JsonSerializerOptions options)
57+
{
58+
writer.WriteStringValue(GetJsonValueForEnum(value));
59+
}
60+
61+
/// <inheritdoc />
62+
public override bool CanConvert(Type typeToConvert) =>
63+
typeToConvert.IsEnum;
64+
65+
private static string GetJsonValueForEnum(T enumValue) =>
66+
FindJsonValueForEnum(enumValue)
67+
.Map(x => x.Value)
68+
.OrThrow($"Missing {nameof(JsonEnumValueAttribute)} on member: {enumValue.GetType().FullName}.{enumValue.ToString("G")}");
69+
70+
private static Maybe<JsonEnumValueAttribute> FindJsonValueForEnum(T enumValue)
71+
{
72+
var enumName = Enum.GetName(enumValue.GetType(), enumValue) ?? throw new ApplicationException($"Unable to find enum member {enumValue} on type {enumValue.GetType().FullName}");
73+
74+
return enumValue.GetType()
75+
.GetMember(enumName)
76+
.Select(x => x.GetCustomAttribute<JsonEnumValueAttribute>(inherit: true))
77+
.Where(x => x is not null)
78+
.Cast<JsonEnumValueAttribute>()
79+
.FirstOrEmpty();
80+
}
81+
82+
private static Maybe<T> FindEnumByJsonValue(string input) =>
83+
Enum.GetValues<T>()
84+
.Select(x => (EnumValue: x, JsonValue: FindJsonValueForEnum(x)))
85+
.FirstOrEmpty(x => x.JsonValue.Try(out var jsonValue) && jsonValue.Value.Equals(input, jsonValue.ComparisonMode))
86+
.Map(x => x.EnumValue);
87+
}

0 commit comments

Comments
 (0)