JsonStringEnumConverter(System.Text.Json)支持空值吗?

21

我正在将我的代码从 .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似乎不支持此功能。

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


1
JsonStringEnumConverter对可空枚举的本地支持正在https://github.com/dotnet/corefx/issues/41307上进行跟踪。 - Nitin Agarwal
@NitinAgarwal 希望它能尽快实现! - Svek
6个回答

18

很遗憾,目前System.Text.Json不支持将可空枚举类型转换为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());
    }
}

希望这能帮到您,直到有原生支持而无需自定义转换器!

11

从现在开始,5.0版本支持使用JsonConverterAttribute指定Nullable<T>基础类型的转换器。

(参考链接)

7

对于Dotnet 6,这是开箱即用的:

public enum UserStatus
{
    NotConfirmed,
    Active,
    Deleted
}

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

    [JsonConverter(typeof(JsonStringEnumConverter))]
    public UserStatus? Status { get; set; }
}

在 Dotnet fiddle 上试一试

查看官方文档


3

我发现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());
    }
}

我在我的解决方案中省略了一些不需要的元素。

希望这对某些人有所帮助。


2

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

        JsonSerializerOptions JsonOptions = new()
        {
            Converters =
            {
                new JsonNullableStringEnumConverter(),
            },
        };
< p > 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);
            }
        }
    }

-2

你应该能够通过安装Newtonsoft JSON nuget并将以下代码放置于您的代码中来恢复原始行为。我猜您正在迁移ASP应用程序:

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

5
使用ASP.NET Core 3.x所带的更新、更好(性能和长期与Microsoft兼容性)的System.Text.Json,是这个想法的核心。提到的“迁移”是指从2.x升级到3.x。 - Svek
@Svek 我能理解你的感受,然而所有新的Json Core功能都存在一些缺陷,因此我们团队决定暂时采用这种方法,我希望它对其他人也有所帮助,因为它回答了你的问题 - “如何解决这个问题?” - Michal Hosala

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接