C# 扩展方法 对象和XML字符串相互转换

发布时间 2024-01-01 16:13:23作者: Yan之有理
public static class ObjectExtensions
{
    // 扩展方法:将对象转换为XML字符串
    public static string ToXml(this object? obj, string? deserializeRootElementName = null, bool writeArrayAttribute = false)
        => obj.ToXml(deserializeRootElementName, writeArrayAttribute, SaveOptions.DisableFormatting);

    // 扩展方法:将对象转换为XML字符串,同时提供保存选项
    public static string ToXml(this object? obj, string? deserializeRootElementName, bool writeArrayAttribute, SaveOptions options)
    {
        // 如果对象为空,则返回空字符串
        if (obj == null) { return string.Empty; }

        // 使用Json.NET将对象序列化为JSON,然后转换为XNode对象
        var xDocument = Newtonsoft.Json.JsonConvert.DeserializeXNode(obj.ToJson(), deserializeRootElementName, writeArrayAttribute);

        // 如果XNode对象为空或转换失败,则返回空字符串;否则,以指定的格式选项返回XML字符串
        return xDocument == null ? string.Empty : xDocument.ToString(options);
    }

    // 扩展方法:从XML字符串反序列化为指定类型的对象
    public static T? FromXml<T>(this string xml) where T : class
    {
        // 如果XML字符串为空,则返回默认值
        if (string.IsNullOrEmpty(xml)) { return default; }

        // 创建XML文档对象并加载XML字符串
        XmlDocument xmlDoc = new XmlDocument();
        xmlDoc.LoadXml(xml);

        // 查找类型T中所有List属性的名称
        var listProperties = FindListPropertiesName(typeof(T));

        // 遍历所有List属性名,并确保XML中对应的元素有重复节点(用于修复可能导致反序列化问题的单个列表项)
        foreach (var propertyName in listProperties)
        {
            var xmlNodeList = xmlDoc.GetElementsByTagName(propertyName);
            if (xmlNodeList is { Count: 1 })
            {
                var xmlNode = xmlNodeList[0];
                if (xmlNode is { ParentNode.ParentNode: not null })
                {
                    xmlNode.ParentNode.AppendChild(xmlNode.CloneNode(true));
                }
            }
        }

        // 将修改后的XML文档转换回JSON字符串
        string jsonResult = Newtonsoft.Json.JsonConvert.SerializeXmlNode(xmlDoc);

        // 设置JSON反序列化选项并添加自定义日期时间转换器
        JsonSerializerOptions options = new JsonSerializerOptions();
        options.Converters.Add(new DatetimeJsonConverter());

        // 使用System.Text.Json从JSON字符串反序列化为指定类型的对象
        var result = JsonSerializer.Deserialize<T>(jsonResult, options);

        // 返回反序列化的对象
        return result;
    }

    // 私有辅助方法:查找给定类型中所有List<>类型的属性名称
    private static List<string> FindListPropertiesName(Type type)
    {
        List<string> listProperties = new List<string>();

        // 获取当前类型及其所有嵌套类型的属性信息
        PropertyInfo[] properties = type.GetAllProperties();

        foreach (PropertyInfo property in properties)
        {
            // 检查属性是否为List<>类型
            if (property.PropertyType.IsGenericType && property.PropertyType.GetGenericTypeDefinition() == typeof(List<>))
            {
                string propertyName = property.Name;

                // 如果属性上有JsonPropertyName特性,则使用特性中的名称
                var attribute = property.GetCustomAttribute<JsonPropertyNameAttribute>();
                if (attribute != null)
                {
                    propertyName = attribute.Name;
                }

                listProperties.Add(propertyName);
            }
            // 如果属性是类类型且不是字符串,则递归查找该类类型中的List<>属性
            else if (property.PropertyType.IsClass && property.PropertyType != typeof(string))
            {
                List<string> nestedListProperties = FindListPropertiesName(property.PropertyType);
                listProperties.AddRange(nestedListProperties);
            }
        }

        return listProperties;
    }

    // 私有辅助方法:获取当前类型及其嵌套类型的全部属性
    private static PropertyInfo[] GetAllProperties(this Type type)
    {
        var properties = new List<PropertyInfo>();

        foreach (var property in type.GetProperties())
        {
            properties.Add(property);

            // 如果属性是类类型且不是字符串,则递归获取其内部的属性
            if (property.PropertyType.IsClass && property.PropertyType != typeof(string))
            {
                var nestedProperties = GetNestedProperties(property.PropertyType);
                properties.AddRange(nestedProperties);
            }
        }

        return properties.ToArray();
    }

    // 私有辅助方法:递归获取嵌套类型的全部属性
    private static List<PropertyInfo> GetNestedProperties(Type type)
    {
        var properties = new List<PropertyInfo>();

        foreach (var property in type.GetProperties())
        {
            properties.Add(property);

            // 如果属性是类类型且不是字符串,则继续查找该类类型内部的属性
            if (property.PropertyType.IsClass && property.PropertyType != typeof(string))
            {
                var nestedProperties = GetNestedProperties(property.PropertyType);
                properties.AddRange(nestedProperties);
            }
        }

        return properties;
    }
}

这个扩展方法提供了四个功能:

  1. ToXml方法:将对象转换为XML字符串。可以传入可选的参数deserializeRootElementName和writeArrayAttribute,用于指定反序列化的根元素名称和是否写入数组属性。还可以通过options参数指定保存选项,以指定返回的XML字符串格式。

  2. FromXml方法:从XML字符串反序列化为指定类型的对象。在反序列化之前,该方法会修复可能导致反序列化问题的单个列表项。同时,还会将XML文档转换为JSON字符串,然后使用System.Text.Json进行反序列化。在反序列化之前,可以通过自定义的日期时间转换器进行自定义日期时间的转换。

  3. FindListPropertiesName私有方法:查找给定类型中所有List<>类型的属性名称。该方法使用递归来查找嵌套类型中的List<>属性。

  4. GetAllProperties和GetNestedProperties私有方法:用于获取给定类型及其嵌套类型的全部属性。这些方法使用递归来查找嵌套类型的属性。

使用这些扩展方法,您可以方便地在C#应用程序中进行对象和XML字符串之间的相互转换。