【问题标题】:How to serialise FlagsAttribute enum's as an array using System.Text.Json?如何使用 System.Text.Json 将 FlagsAttribute 枚举序列化为数组?
【发布时间】:2022-06-26 22:33:25
【问题描述】:

我想使用 System.Text.Json 将 FlagsAttribute 枚举序列化为数组。

当使用内置的 JsonStringEnumConverter 时,这些类型的枚举被序列化为逗号分隔的字符串,例如"Red, Black".

我更喜欢序列化为["Red", "Black"]

【问题讨论】:

    标签: c# json system.text.json


    【解决方案1】:

    如下实现自定义转换器和工厂:

    /// <summary>
    /// JSON serialization factory for `[Flags]` based `enum's` as `string[]`
    /// </summary>
    /// <see href="https://stackoverflow.com/a/59430729/5219886">based on this model</see>
    public class EnumWithFlagsJsonConverterFactory : JsonConverterFactory
    {
      public EnumWithFlagsJsonConverterFactory() { }
    
      public override bool CanConvert(Type typeToConvert)
      {
        // https://github.com/dotnet/runtime/issues/42602#issue-706711292
        return typeToConvert.IsEnum && typeToConvert.IsDefined(typeof(FlagsAttribute), false);
      }
    
      public override JsonConverter CreateConverter(Type typeToConvert, JsonSerializerOptions options)
      {
        var converterType = typeof(EnumWithFlagsJsonConverter<>).MakeGenericType(typeToConvert);
        return (JsonConverter)Activator.CreateInstance(converterType);
      }
    }
    
    /// <summary>
    /// JSON serialization for `[Flags]` based `enum's` as `string[]`
    /// </summary>
    /// <see href="https://github.com/dotnet/runtime/issues/31081#issuecomment-848697673">based on this model</see>
    public class EnumWithFlagsJsonConverter<TEnum> : JsonConverter<TEnum> where TEnum : struct, System.Enum
    {
      private readonly Dictionary<TEnum, string> _enumToString = new Dictionary<TEnum, string>();
      private readonly Dictionary<string, TEnum> _stringToEnum = new Dictionary<string, TEnum>();
    
      public EnumWithFlagsJsonConverter()
      {
        var type = typeof(TEnum);
        var values = System.Enum.GetValues<TEnum>();
    
        foreach (var value in values)
        {
          var enumMember = type.GetMember(value.ToString())[0];
          var attr = enumMember.GetCustomAttributes(typeof(EnumMemberAttribute), false)
            .Cast<EnumMemberAttribute>()
            .FirstOrDefault();
    
          _stringToEnum.Add(value.ToString(), value);
    
          if (attr?.Value != null)
          {
            _enumToString.Add(value, attr.Value);
            _stringToEnum.Add(attr.Value, value);
          }
          else
          {
            _enumToString.Add(value, value.ToString());
          }
        }
      }
    
      public override TEnum Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
      {
        switch (reader.TokenType)
        {
          case JsonTokenType.Null:
            return default(TEnum);
          case JsonTokenType.StartArray:
            TEnum ret = default(TEnum);
            while (reader.Read())
            {
              if (reader.TokenType == JsonTokenType.EndArray)
                break;
              var stringValue = reader.GetString();
              if (_stringToEnum.TryGetValue(stringValue, out var _enumValue))
              {
                ret = Or(ret, _enumValue);
              }
            }
            return ret;
          default:
            throw new JsonException();
        }
      }
    
      public override void Write(Utf8JsonWriter writer, TEnum value, JsonSerializerOptions options)
      {
        var values = System.Enum.GetValues<TEnum>();
        writer.WriteStartArray();
        foreach (var _value in values)
        {
          if (value.HasFlag(_value))
          {
            var v = Convert.ToInt32(_value);
            if (v == 0)
            {
              // handle "0" case which HasFlag matches to all values
              // --> only write "0" case if it is the only value present
              if (value.Equals(_value))
              {
                writer.WriteStringValue(_enumToString[_value]);
              }
            }
            else
            {
              writer.WriteStringValue(_enumToString[_value]);
            }
          }
        }
        writer.WriteEndArray();
      }
    
      /// <summary>
      /// Combine two enum flag values into single enum value.
      /// </summary>
      // <see href="https://stackoverflow.com/a/24172851/5219886">based on this SO</see>
      static TEnum Or(TEnum a, TEnum b)
      {
        if (Enum.GetUnderlyingType(a.GetType()) != typeof(ulong))
          return (TEnum)Enum.ToObject(a.GetType(), Convert.ToInt64(a) | Convert.ToInt64(b));
        else
          return (TEnum)Enum.ToObject(a.GetType(), Convert.ToUInt64(a) | Convert.ToUInt64(b));
      }
    }
    

    示例用法如下:

    var options = new JsonSerializerOptions() {
      WriteIndented = true,
    };
    options.Converters.Add(
      new EnumWithFlagsJsonConverterFactory()
    );
    var json2 = JsonSerializer.Serialize(<...>, options);
    

    参考资料:

    1. https://github.com/dotnet/runtime/issues/31081#issuecomment-848697673
    2. https://stackoverflow.com/a/59430729/5219886
    3. https://stackoverflow.com/a/24172851/5219886

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2021-07-22
      • 1970-01-01
      • 2014-06-15
      • 1970-01-01
      • 1970-01-01
      • 2014-09-22
      • 2016-03-02
      • 1970-01-01
      相关资源
      最近更新 更多