问题起因
导入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;
}
}