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

