【问题标题】:Does JsonStringEnumConverter (System.Text.Json) support null values?JsonStringEnumConverter (System.Text.Json) 是否支持空值?
【发布时间】:2020-04-10 06:57:57
【问题描述】:

我正在将我的代码从 .NET Core 2.x 转移到 .NET Core 3.x(即使用本机库 System.Text.Json)。在执行此操作时,我遇到了一些问题,即以前的 Newtonsoft.Json 对可空枚举的支持目前没有明确的迁移路径 --- 看起来 .NET Core 3.x 不支持它?。

例如,使用Newtonsoft.Json,JSON 转换器支持可为空的枚举,如下所示:

public enum UserStatus
{
    NotConfirmed,
    Active,
    Deleted
}

public class User
{
    public string UserName { get; set; }

    [JsonConverter(typeof(StringEnumConverter))]  // using Newtonsoft.Json
    public UserStatus? Status { get; set; }       // Nullable Enum
}

当前版本的原生库System.Text.Json,好像不支持这个。

我该如何解决这个问题?我无法迁移我的代码!

【问题讨论】:

标签: c# .net-core system.text.json


【解决方案1】:

很遗憾,System.Text.Json 目前不支持“开箱即用”来转换可为空的枚举。

不过,有一个解决方案是使用您自己的自定义转换器(见下文)


解决方案。使用自定义转换器。

您可以通过使用自定义转换器装饰它来将其附加到您的属性:

// using System.Text.Json
[JsonConverter(typeof(StringNullableEnumConverter<UserStatus?>))]  // Note the '?'
public UserStatus? Status { get; set; }                            // Nullable Enum

这是转换器:

public class StringNullableEnumConverter<T> : JsonConverter<T>
{
    private readonly JsonConverter<T> _converter;
    private readonly Type _underlyingType;

    public StringNullableEnumConverter() : this(null) { }

    public StringNullableEnumConverter(JsonSerializerOptions options)
    {
        // for performance, use the existing converter if available
        if (options != null)
        {
            _converter = (JsonConverter<T>)options.GetConverter(typeof(T));
        }

        // cache the underlying type
        _underlyingType = Nullable.GetUnderlyingType(typeof(T));
    }

    public override bool CanConvert(Type typeToConvert)
    {
        return typeof(T).IsAssignableFrom(typeToConvert);
    }

    public override T Read(ref Utf8JsonReader reader, 
        Type typeToConvert, JsonSerializerOptions options)
    {
        if (_converter != null)
        {
            return _converter.Read(ref reader, _underlyingType, options);
        }

        string value = reader.GetString();

        if (String.IsNullOrEmpty(value)) return default;

        // for performance, parse with ignoreCase:false first.
        if (!Enum.TryParse(_underlyingType, value, 
            ignoreCase: false, out object result) 
        && !Enum.TryParse(_underlyingType, value, 
            ignoreCase: true, out result))
        {
            throw new JsonException(
                $"Unable to convert \"{value}\" to Enum \"{_underlyingType}\".");
        }

        return (T)result;
    }

    public override void Write(Utf8JsonWriter writer, 
        T value, JsonSerializerOptions options)
    {
        writer.WriteStringValue(value?.ToString());
    }
}

希望在无需自定义转换器即可获得本机支持之前有所帮助!

【讨论】:

    【解决方案2】:

    我发现 Svek 的回答非常有帮助,但是我希望转换器与可空以及不可空枚举属性兼容。

    我通过如下调整他的转换器来实现这一点:

    public class JsonNullableEnumStringConverter<TEnum> : JsonConverter<TEnum>
    {
        private readonly bool _isNullable;
        private readonly Type _enumType;
    
        public JsonNullableEnumStringConverter() {
            _isNullable = Nullable.GetUnderlyingType(typeof(TEnum)) != null;
    
            // cache the underlying type
            _enumType = _isNullable ? 
                Nullable.GetUnderlyingType(typeof(TEnum)) : 
                typeof(TEnum);
        }
    
        public override TEnum Read(ref Utf8JsonReader reader,
            Type typeToConvert, JsonSerializerOptions options)
        {
            var value = reader.GetString();
    
            if (_isNullable && string.IsNullOrEmpty(value))
                return default; //It's a nullable enum, so this returns null. 
            else if (string.IsNullOrEmpty(value))
                throw new InvalidEnumArgumentException(
                    $"A value must be provided for non-nullable enum property of type {typeof(TEnum).FullName}");
    
            // for performance, parse with ignoreCase:false first.
            if (!Enum.TryParse(_enumType, value, false, out var result)
                && !Enum.TryParse(_enumType, value, true, out result))
            {
                throw new JsonException(
                    $"Unable to convert \"{value}\" to Enum \"{_enumType}\".");
            }
    
            return (TEnum)result;
        }
    
        public override void Write(Utf8JsonWriter writer,
            TEnum value, JsonSerializerOptions options)
        {
            writer.WriteStringValue(value?.ToString());
        }
    }
    

    我还遗漏了一些我的解决方案中不需要的元素。 希望这对那里的人有所帮助。

    【讨论】:

      【解决方案3】:
      【解决方案4】:

      另一个选项是通过选项配置对可空枚举的支持:

              JsonSerializerOptions JsonOptions = new()
              {
                  Converters =
                  {
                      new JsonNullableStringEnumConverter(),
                  },
              };
      

      JsonNullableStringEnumConverter 的来源如下:

      #nullable enable
      
          public class JsonNullableStringEnumConverter : JsonConverterFactory
          {
              readonly JsonStringEnumConverter stringEnumConverter;
      
              public JsonNullableStringEnumConverter(JsonNamingPolicy? namingPolicy = null, bool allowIntegerValues = true)
              {
                  stringEnumConverter = new(namingPolicy, allowIntegerValues);
              }
      
              public override bool CanConvert(Type typeToConvert)
                  => Nullable.GetUnderlyingType(typeToConvert)?.IsEnum == true;
      
              public override JsonConverter? CreateConverter(Type typeToConvert, JsonSerializerOptions options)
              {
                  var type = Nullable.GetUnderlyingType(typeToConvert)!;
                  return (JsonConverter?)Activator.CreateInstance(typeof(ValueConverter<>).MakeGenericType(type),
                      stringEnumConverter.CreateConverter(type, options));
              }
      
              class ValueConverter<T> : JsonConverter<T?>
                  where T : struct, Enum
              {
                  readonly JsonConverter<T> converter;
      
                  public ValueConverter(JsonConverter<T> converter)
                  {
                      this.converter = converter;
                  }
      
                  public override T? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
                  {
                      if (reader.TokenType == JsonTokenType.Null)
                      {
                          reader.Read();
                          return null;
                      }
                      return converter.Read(ref reader, typeof(T), options);
                  }
      
                  public override void Write(Utf8JsonWriter writer, T? value, JsonSerializerOptions options)
                  {
                      if (value == null)
                          writer.WriteNullValue();
                      else
                          converter.Write(writer, value.Value, options);
                  }
              }
          }
      

      【讨论】:

        【解决方案5】:

        您应该能够通过安装 Newtonsoft JSON nuget 并将其放入您的代码中恢复原来的行为,我想您正在迁移一个 ASP 应用程序:

        public void ConfigureServices(IServiceCollection services)
        {
            services.AddControllers()
                .AddNewtonsoftJson();
        }
        

        【讨论】:

        • 我们的想法是使用 ASP.NET Core 3.x 附带的更新的、“更好的”(长期的性能和 Microsoft 兼容性)System.Text.Json。 --- 提到的“迁移”是从 2.x 到 3.x
        • @Svek 我可以理解这一点,但是,所有新的闪亮的 Json 核心功能都有一些差距,所以暂时离开团队决定宁愿使用这种方法,我希望这对其他一些人有所帮助人们也一样,因为它以原始形式回答了您的问题 - “如何解决这个问题?”。
        猜你喜欢
        • 2015-06-23
        • 2014-03-17
        • 2012-09-26
        • 1970-01-01
        • 1970-01-01
        • 2023-03-06
        • 2011-03-21
        • 2015-09-03
        • 1970-01-01
        相关资源
        最近更新 更多