如何使用Newtonsoft将数字反序列化为枚举值?解决类型转换异常
问题:.NET 5自定义模型绑定器反序列化枚举时类型转换错误
在.NET 5 Web应用中使用自定义模型绑定器时,遇到运行异常:模型中PhoneNumbers列表里的每个PhoneNumber对象的PhoneType枚举反序列化失败,错误信息为:Unable to cast object of type 'System.Double' to type 'PhoneType'。
自定义模型绑定器代码
public class JsonReferenceTypeModelBinder : IModelBinder { private readonly Type _modelType; public JsonReferenceTypeModelBinder(Type modelType) { _modelType = modelType; } public async Task BindModelAsync(ModelBindingContext bindingContext) { if (bindingContext == null) throw new ArgumentNullException(nameof(bindingContext)); object item = null; var serializer = new JsonSerializer { NullValueHandling = NullValueHandling.Ignore }; serializer.Converters.Add(new DecimalSafeNumericJsonConverter()); var json = Encoding.UTF8.GetString(await bindingContext.HttpContext.Request.Body.GetBytesAsync()); try { using var textReader = new StringReader(json); using var reader = new JsonTextReader(textReader); item = serializer.Deserialize(reader, _modelType); } catch (JsonReaderException jsonEx) { // in case the body was url-encoded instead of serialized as json // (use case: ajax controller actions utilizing [ValidateAntiForgeryToken] appear to not allow requests formatted as json) var keyValues = new Dictionary<string, string>(); var props = json.Split('&'); foreach (var prop in props) { var keyAndValue = prop.Split('='); var decodedValue = HttpUtility.UrlDecode(keyAndValue[1]); keyValues.Add(keyAndValue[0], decodedValue); } json = JsonConvert.SerializeObject(keyValues); using var textReader = new StringReader(json); using var reader = new JsonTextReader(textReader); item = serializer.Deserialize(reader, _modelType); } catch (Exception ex) { return; } if (item != null) bindingContext.Result = ModelBindingResult.Success(item); } }
CompanyContact类代码
public class CompanyContact { private string _email = string.Empty; private string _firstName = string.Empty; private string _lastName = string.Empty; private string _title = string.Empty; private List<Enums.ContactType> _contactType { get; set; } = new List<Enums.ContactType>(); private List<Phone> _phoneNumbers { get; set; } = new List<Phone>(); public int CompanyId { get; set; } public string id { get; set; } = string.Empty; public string FirstName { get => _firstName ?? string.Empty; set => _firstName = value; } public List<Email> Emails { get; set; } = new List<Email>(); public Address Address { get; set; } = new Address(); public string LastName { get => _lastName ?? string.Empty; set => _lastName = value; } public string Email { get => _email ?? string.Empty; set => _email = value; } public string PhoneNumber { get; set; } = string.Empty; public List<Enums.ContactType> ContactType { get => _contactType; set => _contactType = value; } public List<Phone> PhoneNumbers { get => _phoneNumbers; set => _phoneNumbers = value; } public string Title { get => _title ?? string.Empty; set => _title = value; } }
Phone类代码
public class Phone { private string _number = string.Empty; public Enums.PhoneType PhoneType { get; set; } public string Number { get => _number ?? string.Empty; set => _number = value; } }
PhoneType枚举代码
public enum PhoneType { Home = 1, Work, Cellular, Pager, Fax, WorkFax, Alternate }
示例JSON
{ "FirstName": "Jim", "LastName": "Miller", "Title": "President", "PhoneNumbers": [ { "Number": "123-123-1234", "PhoneType": 2 } ], "Emails": [ { "Address": "jmiller@somecompany.com", "EmailType": 2 } ] }
解决方案
原因分析
问题出在JSON反序列化时,枚举类型无法直接将解析后的Double类型(JSON数字默认解析为浮点型)转换为整型枚举。另外,URL编码转JSON的分支中,序列化后的枚举值可能被处理为字符串或浮点型,进一步加剧了类型不匹配问题。
解决步骤
1. 实现自定义枚举转换器
创建一个通用的枚举转换器,支持将浮点型、整型、字符串类型的输入转换为枚举:
public class NumericEnumConverter : JsonConverter { public override bool CanConvert(Type objectType) { return objectType.IsEnum || (objectType.IsGenericType && objectType.GetGenericTypeDefinition() == typeof(Nullable<>) && Nullable.GetUnderlyingType(objectType).IsEnum); } public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) { if (reader.TokenType == JsonToken.Null) { if (!objectType.IsNullableEnum()) throw new JsonSerializationException($"无法将null值转换为{objectType.Name}"); return null; } Type enumType = objectType.IsNullableEnum() ? Nullable.GetUnderlyingType(objectType) : objectType; object value; if (reader.TokenType == JsonToken.String) { string enumText = reader.Value.ToString(); if (string.IsNullOrEmpty(enumText) && objectType.IsNullableEnum()) return null; value = Enum.Parse(enumType, enumText, ignoreCase: true); } else if (reader.TokenType == JsonToken.Integer || reader.TokenType == JsonToken.Float) { long numericValue = Convert.ToInt64(reader.Value); value = Enum.ToObject(enumType, numericValue); } else { throw new JsonSerializationException($"枚举{enumType.Name}不支持{reader.TokenType}类型的输入"); } return value; } public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) { if (value == null) { writer.WriteNull(); return; } writer.WriteValue(Convert.ToInt64(value)); } } // 扩展方法:判断是否为可空枚举 public static class TypeExtensions { public static bool IsNullableEnum(this Type type) { Type underlyingType = Nullable.GetUnderlyingType(type); return underlyingType != null && underlyingType.IsEnum; } }
2. 在模型绑定器中注册转换器
修改JsonReferenceTypeModelBinder的序列化器配置,添加自定义枚举转换器:
var serializer = new JsonSerializer { NullValueHandling = NullValueHandling.Ignore }; serializer.Converters.Add(new DecimalSafeNumericJsonConverter()); serializer.Converters.Add(new NumericEnumConverter()); // 新增该行
3. 验证修复效果
重新运行应用后,使用示例JSON请求,PhoneType字段会被正确从数字2转换为PhoneType.Work,不再抛出类型转换异常。
内容的提问来源于stack exchange,提问作者geoff swartz
相关产品推荐
相关产品推荐

