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

如何使用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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.18 07:32:02