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

}
相关推荐
楽码19 分钟前
底层技术SwissTable的实现对比
数据结构·后端·算法
m0_480502641 小时前
Rust 入门 泛型和特征-特征对象 (十四)
开发语言·后端·rust
程序员爱钓鱼1 小时前
Go语言实战案例-使用ORM框架 GORM 入门
后端
M1A11 小时前
TCP协议详解:为什么它是互联网的基石?
后端·网络协议·tcp/ip
一枚小小程序员哈1 小时前
基于微信小程序的家教服务平台的设计与实现/基于asp.net/c#的家教服务平台/基于asp.net/c#的家教管理系统
后端·c#·asp.net
楽码2 小时前
自动修复GoVet:语言实现对比
后端·算法·编程语言
石榴树下2 小时前
00. 马里奥的 OAuth 2 和 OIDC 历险记
后端
uhakadotcom2 小时前
开源:subdomainpy快速高效的 Python 子域名检测工具
前端·后端·面试
似水流年流不尽思念2 小时前
容器化技术了解吗?主要解决什么问题?原理是什么?
后端
Java水解2 小时前
Java中的四种引用类型详解:强引用、软引用、弱引用和虚引用
java·后端