You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

System.Text.Json自定义枚举序列化兼容性异常求助

解决System.Text.Json自定义枚举转换器与Nullable兼容性问题及多格式枚举序列化需求

问题根源

你遇到的兼容性异常,是因为自定义的CustomStringEnumConverter继承了JsonConverter<Enum?>,但System.Text.Json在处理Nullable<Allowances>这类具体可空枚举类型时,需要的是针对该具体类型的转换器,而非泛型的Enum?转换器。JsonConverterFactory必须为每个具体枚举类型(包括可空版本)生成对应的转换器实例,才能匹配类型要求。

解决方案

修改转换器实现,将内部转换器改为泛型结构,让工厂类根据目标类型动态创建对应泛型转换器实例,同时优化枚举解析逻辑:

修改后的完整转换器代码

public class AlternativeValueJsonStringEnumConverter : JsonConverterFactory
{
    public override bool CanConvert(Type typeToConvert)
    {
        var enumType = Nullable.GetUnderlyingType(typeToConvert) ?? typeToConvert;
        return enumType.IsEnum;
    }

    public override JsonConverter? CreateConverter(Type typeToConvert, JsonSerializerOptions options)
    {
        var isNullable = Nullable.GetUnderlyingType(typeToConvert) != null;
        var enumType = isNullable ? Nullable.GetUnderlyingType(typeToConvert)! : typeToConvert;

        // 动态创建泛型转换器实例
        var converterType = typeof(CustomStringEnumConverter<>).MakeGenericType(enumType);
        return (JsonConverter?)Activator.CreateInstance(converterType, options);
    }

    private class CustomStringEnumConverter<TEnum> : JsonConverter<TEnum> where TEnum : struct, Enum
    {
        public CustomStringEnumConverter(JsonSerializerOptions options) { }

        public override TEnum Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
        {
            var isNullable = Nullable.GetUnderlyingType(typeToConvert) != null;

            switch (reader.TokenType)
            {
                case JsonTokenType.Null:
                    if (!isNullable)
                        throw new JsonException("无法将null值反序列化为非可空枚举字段");
                    return default;
                case JsonTokenType.String:
                    var stringValue = reader.GetString()!;
                    if (TryParseFromString(stringValue, out TEnum result))
                        return result;
                    throw new JsonException($"无法将字符串'{stringValue}'转换为枚举类型'{typeof(TEnum).Name}'");
                case JsonTokenType.Number:
                    var numberValue = reader.GetInt32();
                    if (Enum.IsDefined(typeof(TEnum), numberValue))
                        return (TEnum)Enum.ToObject(typeof(TEnum), numberValue);
                    throw new JsonException($"无法将数值'{numberValue}'转换为枚举类型'{typeof(TEnum).Name}'");
                default:
                    throw new JsonException($"不支持的JSON令牌类型{reader.TokenType}用于枚举反序列化");
            }
        }

        public override void Write(Utf8JsonWriter writer, TEnum value, JsonSerializerOptions options)
        {
            // 序列化时输出枚举名称,如需输出AlternativeValue可修改此处逻辑
            writer.WriteStringValue(value.ToString());
        }

        private bool TryParseFromString(string input, out TEnum result)
        {
            // 尝试直接解析枚举名称(大小写不敏感)
            if (Enum.TryParse(input, true, out result))
                return true;

            // 尝试匹配AlternativeValue属性
            foreach (var enumValue in Enum.GetValues<TEnum>())
            {
                var fieldInfo = typeof(TEnum).GetField(enumValue.ToString());
                var altAttr = fieldInfo?.GetCustomAttribute<AlternativeValueAttribute>();
                if (altAttr != null && altAttr.Code.Equals(input, StringComparison.OrdinalIgnoreCase))
                {
                    result = enumValue;
                    return true;
                }
            }

            return false;
        }
    }
}

关键修改说明

  1. 泛型转换器适配具体枚举类型:将内部转换器改为CustomStringEnumConverter<TEnum>,约束TEnum为值类型且是枚举,确保每个具体枚举类型都有对应的转换器实例,解决Nullable类型兼容性问题。
  2. 动态创建转换器实例:在工厂类的CreateConverter方法中,通过反射生成对应枚举类型的泛型转换器实例,适配可空与非可空枚举。
  3. 优化解析逻辑:
    • 拆分字符串解析逻辑为独立方法,更清晰易维护
    • 修复数值解析的bug:原代码用reader.GetString()读取数值会报错,改为reader.GetInt32()(如果枚举是其他数值类型可调整为GetInt64()等)
    • 增加异常抛出的明确性,便于调试
  4. 简化可空类型处理:利用泛型约束和默认值处理可空枚举的null情况,无需额外类型判断

测试验证

原测试代码可直接运行,同时建议补充可空枚举的测试用例:

[Fact]
public void ItShouldDeserialiseNullableEnum()
{
    var settings = new JsonSerializerOptions { WriteIndented = false };
    settings.Converters.Add(new AlternativeValueJsonStringEnumConverter());

    var output = JsonSerializer.Deserialize<AllowanceRequest>("{ Type: 'AD', AdditionalType: null, Value: 15.5 }", settings);
    output.Should().BeEquivalentTo(new { Type = Allowances.Transport, AdditionalType = (Allowances?)null, Value = 15.5M });
}

[Fact]
public void ItShouldDeserialiseEnumNumberValue()
{
    var settings = new JsonSerializerOptions { WriteIndented = false };
    settings.Converters.Add(new AlternativeValueJsonStringEnumConverter());

    var output = JsonSerializer.Deserialize<AllowanceRequest>("{ Type: 2, Value: 30.0 }", settings);
    output.Should().BeEquivalentTo(new { Type = Allowances.Laundry, Value = 30.0M });
}

内容的提问来源于stack exchange,提问作者David Keaveny

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.23 06:14:57