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;
    }

}
相关推荐
大学生资源网17 分钟前
基于springboot的万亩助农网站的设计与实现源代码(源码+文档)
java·spring boot·后端·mysql·毕业设计·源码
苏三的开发日记26 分钟前
linux端进行kafka集群服务的搭建
后端
苏三的开发日记44 分钟前
windows系统搭建kafka环境
后端
m0_555762901 小时前
CMakePresets.json与cmake区别
json
爬山算法1 小时前
Netty(19)Netty的性能优化手段有哪些?
java·后端
Tony Bai1 小时前
Cloudflare 2025 年度报告发布——Go 语言再次“屠榜”API 领域,AI 流量激增!
开发语言·人工智能·后端·golang
想用offer打牌1 小时前
虚拟内存与寻址方式解析(面试版)
java·后端·面试·系统架构
無量1 小时前
AQS抽象队列同步器原理与应用
后端
9号达人2 小时前
支付成功订单却没了?MyBatis连接池的坑我踩了
java·后端·面试
用户497357337982 小时前
【轻松掌握通信协议】C#的通信过程与协议实操 | 2024全新
后端