C# JSON 反序列化时,忽略转换失败的属性 JTokenSafeToExtensions

问题起因

导入Excel数据时,获取的数据不规范,转换JSON,反序列化直接出错

环境是.net8,因为使用全局引用,所以少了一下using没标识出来

cs 复制代码
using System.Collections;
using System.Reflection;
using Newtonsoft.Json;

public static class JTokenSafeToExtensions
{
    /// <summary>
    /// 安全转换JToken为目标类型,忽略转换失败的字段/元素
    /// </summary>
    /// <param name="token">源JToken</param>
    /// <param name="targetType">目标类型</param>
    /// <param name="serializer"></param>
    /// <returns>转换后的对象(可能部分字段未转换)</returns>
    public static object? SafeTo(this JToken? token, Type targetType, JsonSerializer? serializer = null)
    {
        try
        {
            // 如果token为null或者类型为JTokenType.Null,则返回默认值
            if (token == null || token.Type == JTokenType.Null)
            {
                return GetDefaultValue(targetType);
            }

            // 处理基础类型和字符串
            if (IsPrimitiveOrString(targetType))
            {
                // 尝试将token转换为基础类型或字符串,如果转换失败,则返回默认值
                return TryConvertPrimitive(token, targetType, serializer) ?? GetDefaultValue(targetType);
            }

            // 处理集合类型
            if (IsEnumerable(targetType))
            {
                // 将token转换为集合类型
                return ConvertEnumerable(token, targetType);
            }

            // 处理自定义对象
            return ConvertComplexObject(token, targetType);
        }
        catch
        {
            // 如果发生异常,则返回默认值
            return GetDefaultValue(targetType);
        }
    }


    public static T? SafeTo<T>(this JToken? token, JsonSerializer? serializer = null)
        => (T?)SafeTo(token, typeof(T), serializer);

    private static object? GetDefaultValue(Type type)
    {
        // 如果类型是类、接口或数组,则返回null,否则返回类型的默认值
        return type.IsClass || type.IsInterface || type.IsArray ? null : Activator.CreateInstance(type);
    }

    private static bool IsPrimitiveOrString(Type type)
    {
        // 判断类型是否为基础类型、字符串、日期时间、Guid或时间间隔
        return type.IsPrimitive ||
               type == typeof(string) ||
               type == typeof(DateTime) ||
               type == typeof(Guid) ||
               type == typeof(TimeSpan);
    }

    private static object? TryConvertPrimitive(JToken token, Type targetType, JsonSerializer? serializer = null)
    {
        try
        {
            // 优先使用原生转换
            return serializer is null ? token.ToObject(targetType) : token.ToObject(targetType, serializer);
        }
        catch
        {
            // 原生转换失败时尝试手动解析
            object? value = null;
            switch (targetType)
            {
                case { } t when t == typeof(int) || t == typeof(int?):
                {
                    value = int.TryParse(token.ToString(), out int val) ? val : (int?)null;
                    break;
                }

                case { } t when t == typeof(long) || t == typeof(long?):
                {
                    value = long.TryParse(token.ToString(), out long val) ? val : (long?)null;
                    break;
                }
                case { } t when t == typeof(double) || t == typeof(double?):
                {
                    value = double.TryParse(token.ToString(), out double val)
                        ? val
                        : (double?)null;
                    break;
                }
                case { } t when t == typeof(decimal) || t == typeof(decimal?):
                {
                    value = decimal.TryParse(token.ToString(), out decimal val)
                        ? val
                        : (decimal?)null;
                    break;
                }
                case { } t when t == typeof(bool) || t == typeof(bool?):
                {
                    value = bool.TryParse(token.ToString(), out bool val) ? val : (bool?)null;
                    break;
                }
                case { } t when t == typeof(DateTime) || t == typeof(DateTime?):
                {
                    value = DateTime.TryParse(token.ToString(), out DateTime val)
                        ? val
                        : (DateTime?)null;
                    break;
                }
                case { } t when t == typeof(Guid) || t == typeof(Guid?):
                {
                    value = Guid.TryParse(token.ToString(), out Guid val) ? val : (Guid?)null;
                    break;
                }
            }

            return value ?? GetDefaultValue(targetType);
        }
    }

    private static bool IsEnumerable(Type type)
    {
        // 判断类型是否为IEnumerable的子类,且不是字符串
        return typeof(IEnumerable).IsAssignableFrom(type) && type != typeof(string);
    }

    private static object ConvertEnumerable(JToken token, Type targetType)
    {
        // 获取集合类型的元素类型
        var elementType = GetEnumerableElementType(targetType);
        // 创建List<T>的实例
        var listType = typeof(List<>).MakeGenericType(elementType);
        var list = (IList)Activator.CreateInstance(listType)!;

        // 遍历token的子元素,将每个子元素转换为元素类型,并添加到List中
        foreach (var item in token.Children())
        {
            var converted = item.SafeTo(elementType);
            if (converted != null || elementType.IsClass) // 允许null值(除非是值类型)
            {
                list.Add(converted);
            }
        }

        // 如果目标类型是数组,则将List转换为数组,否则返回List
        return targetType.IsArray ? list.CopyToArray(targetType) : list;
    }

    private static Type GetEnumerableElementType(Type type)
    {
        // 如果类型是数组,则返回数组元素类型
        if (type.IsArray) return type.GetElementType()!;
        // 如果类型是泛型类型,则返回泛型参数类型
        if (type.IsGenericType) return type.GetGenericArguments().First();

        // 处理非泛型IEnumerable
        var ienumerable = type.GetInterfaces()
            .FirstOrDefault(ti => ti.IsGenericType && ti.GetGenericTypeDefinition() == typeof(IEnumerable<>));
        // 返回IEnumerable的泛型参数类型,如果没有则返回object
        return ienumerable?.GetGenericArguments().First() ?? typeof(object);
    }

    private static object ConvertComplexObject(JToken token, Type targetType)
    {
        // 创建目标类型的实例
        var instance = Activator.CreateInstance(targetType);
        // 获取目标类型的可写属性
        var properties = targetType.GetProperties(BindingFlags.Public | BindingFlags.Instance)
            .Where(p => p.CanWrite)
            .ToList();

        // 遍历属性,将token中的值转换为属性类型,并设置属性值
        foreach (var prop in properties)
        {
            try
            {
                // 获取属性上的JsonPropertyAttribute特性,获取属性名
                var jsonProp = prop.GetCustomAttribute<JsonPropertyAttribute>();
                var propName = jsonProp?.PropertyName ?? prop.Name;
                // 获取token中的属性值
                var jValue = token[propName];

                if (jValue == null) continue;

                // 获取属性的类型
                var propType = prop.PropertyType;
                // 将token中的值转换为属性类型
                var convertedValue = jValue.SafeTo(propType);

                // 设置属性值
                prop.SetValue(instance, convertedValue);
            }
            catch
            {
                /* 忽略单个属性转换错误 */
            }
        }

        return instance;
    }
}

其他:读取Excel并直接反序列化的代码

cs 复制代码
using System.Data;
using System.Drawing;
using OfficeOpenXml;
using OfficeOpenXml.Style;


public static class ExcelHelper
{
    static ExcelHelper()
    {
        // ExcelPackage.LicenseContext = LicenseContext.NonCommercial;
        ExcelPackage.License.SetNonCommercialPersonal("My Name");
    }

    public static List<Dictionary<string, object?>> ReadStream(Stream stream)
    {
        using var package = new ExcelPackage(stream);
        var worksheet = package.Workbook.Worksheets[0];
                
        var data = new List<Dictionary<string, object?>>();
        var headers = worksheet.Cells[1, 1, 1, worksheet.Dimension.End.Column]
            .Select(c => c.Text)
            .ToArray();

        for (int row = 2; row <= worksheet.Dimension.End.Row; row++)
        {
            var rowData = new Dictionary<string, object?>();
            for (int col = 1; col <= headers.Length; col++)
            {
                var value = worksheet.Cells[row, col].Value;
                if (value != null)
                {
                    rowData[headers[col - 1]] = value;
                }
            }

            if (rowData.Count > 0)
            {
                data.Add(rowData);
            }
        }

        return data;
    }

    public static List<T?> ReadStream<T>(Stream stream)
    {
        using var package = new ExcelPackage(stream);
        var worksheet = package.Workbook.Worksheets[0];
                
        var data = new List<T?>();
        var headers = worksheet.Cells[1, 1, 1, worksheet.Dimension.End.Column]
            .Select(c => c.Text)
            .ToArray();

        for (int row = 2; row <= worksheet.Dimension.End.Row; row++)
        {
            var rowData = new JObject();
            for (int col = 1; col <= headers.Length; col++)
            {
                var value = worksheet.Cells[row, col].Value;
                if (value != null)
                {
                    rowData[headers[col - 1]] = new JValue(value);
                }
            }

            if (rowData.Count > 0)
            {
                data.Add(rowData.SafeTo<T>(JsonHelper.DefaultJsonSerializer));
            }
        }

        return data;
    }

}
相关推荐
想当花匠的小码农4 分钟前
golang 项目 OpenTelemetry 实践
后端
Jiude7 分钟前
如何使用 Certbot 为域名配置永久免费的 HTTPS 证书
后端·nginx·https
basketball61614 分钟前
Linux C 进程基本操作
linux·运维·服务器·c语言·后端
ku_code_ku24 分钟前
Django由于数据库版本原因导致数据库迁移失败解决办法
后端·python·django
JavaGuide31 分钟前
感谢数字马力收留,再也不想面试了!!
java·后端
37手游后端团队1 小时前
Eino大模型应用开发框架深入浅出
人工智能·后端
要开心吖ZSH1 小时前
Spring Cloud LoadBalancer 详解
后端·spring·spring cloud
泉城老铁1 小时前
Spring Boot + EasyPOI 实现 Excel 和 Word 导出 PDF 详细教程
java·后端·架构
LovelyAqaurius1 小时前
了解Redis Hash类型
后端
JuiceFS2 小时前
合合信息:基于 JuiceFS 构建统一存储,支撑 PB 级 AI 训练
运维·后端