1. 程式人生 > >xml序列化和反序列化方法

xml序列化和反序列化方法

 

 

1、作用:將實體類的屬性(字串、日期、數值、布而、類)生成為xml文件中的結點,將xml文件的結點值或者屬性值填充到實體類的屬性值中
2、思路:特性、反射、泛型;特性記錄xml結點與實體屬性的對應關係,反射獲取和填充例項中的屬性值。 自定義NodeAttribute,兩個屬性Name和Index,Name用於對應實體類中屬性在xml文件中的結點名稱,Index反應不同結點在xml文件中的出現順序。
3、兩個類:
XmlNodeSerializer,實體<->XmlNode;因為有時候要操作的xml文件結構比較複雜,NodeAttribute不能實現所有需求,所以把簡單基礎的結點在裡做了,複雜的結點自己實現。
XmlSerializer:從XmlNodeSerializer繼承來的,實體<->XmlDocument
4、可設定的部分:
1)TimeFormat:可以設定例項中日期型別的屬性值的輸出格式;
2)NodeNameFormatter:可以對結點名稱格式化,屬性與結點的名稱對應關係a)如果設定了NodeAttribute,從NodeAttribute中取;b)如果沒有NodeAttribute預設屬性名;c)如果設定了NodeNameFormatter,再按NodeNameFormatter的規則格式化
3)CreateNodeWhenNull:屬性值為null時是否生成結點
4)ContainPropertyWithOutNodeAttr:屬性值沒有被NodeAttribute標記時,是否生成、解析結點

 

測試

        public void Test1()
        {
            XmlSerializer xs = new XmlSerializer();
            xs.TimeFormat = "yyyy-MM-dd";
            xs.NodeNameFormatter = Format;
            xs.CreateNodeWhenNull = true;
            xs.ContainPropertyWithOutNodeAttr = true;

            Student stu 
= new Student(); stu.Name = "小小"; stu.Age = 10; stu.BirthDay = new DateTime(2008, 6, 7); Dog dog = new Dog(); dog.Color = "黑色"; dog.Age = 2; dog.Weight = 12.34; //dog.Name //null 測試CreateNodeWhenNull stu.Dog = dog; XmlDocument doc
= xs.Serialize(stu); doc.Save("1.xml"); Student stu2 = xs.DeSerialize<Student>(File.ReadAllText("1.xml")); } private string Format(string attr) { if (string.IsNullOrEmpty(attr)) return null; StringBuilder sb = new StringBuilder(); char[] name = attr.ToCharArray(); for (int i = 0; i < name.Length; i++) { if (i.Equals(0)) { sb.Append(name[i].ToString().ToUpper()); } else { if (name[i] >= 'A' && name[i] <= 'Z') { sb.Append("_").Append(name[i].ToString()); } else { sb.Append(name[i].ToString().ToUpper()); } } } return sb.ToString(); } public class Student { [Node(Index = 2, Name = "MingCheng")] public string Name { get; set; } [Node(Index = 3)] public int Age { get; set; } [Node(Index = 1)] public DateTime? BirthDay { get; set; } [Node(Name = "GouGou")] public Dog Dog { get; set; } } public class Dog { [Node] public string Name { get; set; } [Node] public int? Age { get; set; } [Node] public double? Weight { get; set; } public string Color { get; set; } }

 

原始碼

    /// <summary>
    /// xml結點序列化,反序列化
    /// TimeFormat:設定日期屬性的格式
    /// NodeNameFormatter:可以對結點名稱格式化
    /// 結點名:如果設定了NodeAttr的Name,按設定取,否則預設取屬性名
    /// CreateNodeWhenNull:設定屬性值為null時是否生成結點
    /// ContainPropertyWithOutNodeAttr:屬性值沒有被NodeAttr標記時,是否生成、解析結點
    /// </summary>
    public class XmlNodeSerializer
    {
        protected string timeFormat;
        protected Func<string, string> nodeFormatter;
        protected bool createNodeWhenNull = true;
        protected bool containPropertyWithOutNodeAttr = false;


        /// <summary>
        /// 日期格式
        /// </summary>
        public string TimeFormat { set { timeFormat = value; } }

        /// <summary>
        /// 結點名稱格式化器
        /// </summary>
        public Func<string, string> NodeNameFormatter { set { nodeFormatter = value; } }

        /// <summary>
        /// 空屬性值是否生成結點
        /// </summary>
        public bool CreateNodeWhenNull { set { createNodeWhenNull = value; } }

        /// <summary>
        /// 屬性沒有被NodeAttr標記時,是否生成結點或解析,預設false
        /// </summary>
        public bool ContainPropertyWithOutNodeAttr { set { containPropertyWithOutNodeAttr = value; } }

        //如果node需要新增到doc中,則需要將doc作引數傳入
        public XmlNode Serialize<T>(T data, XmlDocument doc)
        {
            Type type = typeof(T);
            string nodeName = string.Empty; //結點名
            NodeAttribute na = type.GetCustomAttribute<NodeAttribute>();
            if (na == null || na.Name.IsNullOrEmpty())
            {
                nodeName = type.Name;//如果沒有設定,使用類名
            }
            else
            {
                nodeName = na.Name; //取設定的結點名
            }
            if (nodeFormatter != null) nodeName = nodeFormatter(nodeName);
            return Serialize(data, typeof(T), nodeName, doc);
        }

        public XmlNode Serialize<T>(T data)
        {
            return Serialize(data, new XmlDocument());
        }

        private XmlNode Serialize(object data, Type type, string parentNodeName, XmlDocument doc)
        {
            if (data == null || parentNodeName.IsNullOrEmpty() || type == null || doc == null) return null;
            XmlNode root = doc.CreateElement(parentNodeName);
            Entity ent = new Entity();
            //對自定義類、基礎資料型別的屬性值生成節點
            var props = type.GetProperties().Where(p => ent.IsUserDefinedClass(p) || ent.IsNormalBaseType(p));
            //有註解的屬性
            var selfNodeProps = props.Where(p =>
            {
                var cus = p.GetCustomAttributes(typeof(NodeAttribute), true);
                return cus != null && cus.Count() > 0;
            }).OrderBy(p =>
            {
                NodeAttribute na = p.GetCustomAttributes(typeof(NodeAttribute), true)[0] as NodeAttribute;
                int i = na.Index;
                Console.WriteLine($"{na.Name},{na.Index}");
                return i;
            }).ToArray();
            List<PropertyInfo> pis = new List<PropertyInfo>();
            //按註解裡的先後順序新增結點
            if (selfNodeProps != null && selfNodeProps.Count() > 0)
            {
                pis.AddRange(selfNodeProps);
            }
            if (containPropertyWithOutNodeAttr)
            {
                //沒有註解的屬性
                var defaultNodeProps = props.Where(p =>
                {
                    var cus = p.GetCustomAttributes(typeof(NodeAttribute), true);
                    return cus == null || cus.Count() <= 0;
                });
                //再新增沒有註解的結點
                if (defaultNodeProps != null && defaultNodeProps.Count() > 0)
                {
                    pis.AddRange(defaultNodeProps);
                }
            }
            foreach (var prop in pis)
            {
                string nodeName = prop.Name;//結點名,預設沒有註解,用屬性名
                var attrs = prop.GetCustomAttributes(typeof(NodeAttribute), true);
                if (attrs != null && attrs.Count() > 0)
                {
                    NodeAttribute attr = attrs[0] as NodeAttribute;
                    if (attr.Name.IsNotNullOrEmpty())//註解裡有結點名
                    {
                        nodeName = attr.Name;
                    }
                }
                if (nodeFormatter != null)//如果設定了更改規則,按規則修改
                {
                    nodeName = nodeFormatter(nodeName);
                }
                if (ent.IsNormalBaseType(prop))//基礎資料型別,直接生成結點
                {
                    string nodeValue = null;
                    object v = prop.GetValue(data);
                    if (prop.PropertyType.ToString().Contains("DateTime"))
                    {
                        if (v != null)
                        {
                            if (timeFormat.IsNullOrEmpty()) timeFormat = "yyyyMMdd";
                            nodeValue = Convert.ToDateTime(v).ToString(timeFormat);
                        }
                    }
                    else if (v != null)
                    {
                        nodeValue = Convert.ToString(v);
                    }
                    if (nodeValue == null && !createNodeWhenNull)
                    {
                        continue;//屬性值為null,且設定了null值不需要加入結點
                    }
                    XmlNode node = doc.CreateElement(nodeName);//建立結點
                    node.AppendChild(doc.CreateCDataSection(nodeValue));
                    root.AppendChild(node);//拼接到根結點中
                }
                else if (ent.IsUserDefinedClass(prop))//自定義類,生成複雜結點
                {
                    object v = prop.GetValue(data);
                    if (v != null)
                    {
                        XmlNode node = Serialize(v, prop.PropertyType, nodeName, doc);
                        if (node != null)
                        {
                            root.AppendChild(node);
                        }
                    }
                }
            }
            return root;
        }

        //對基礎資料型別屬性賦值
        private void FillBaseType<T>(XmlNode node, T obj, IEnumerable<PropertyInfo> baseProps)
        {
            DataType myType = new DataType();
            foreach (var prop in baseProps)
            {
                string name = GetNodeNameFromProp(prop);
                XmlNode xn = node.SelectSingleNode(name);
                string xnv = string.Empty;
                if (xn == null)
                {
                    //如果沒有對應結點則從屬性中找
                    var attr = node.Attributes[name];
                    if (attr != null && attr.Value.IsNotNullOrEmpty())
                    {
                        xnv = attr.Value;
                    }
                    else
                    {
                        continue;
                    }
                }
                else if (xn.InnerText.IsNullOrEmpty())
                {
                    continue;
                }
                else
                {
                    xnv = xn.InnerText;
                }
                //取屬性的資料型別
                DbType dbt = myType.PropertyTypeToDbType(prop.PropertyType.ToString());
                switch (dbt)
                {
                    case DbType.AnsiString:
                        prop.SetValue(obj, xnv, null); break;
                    case DbType.DateTime:
                        DateTime dt;
                        if (timeFormat.IsNullOrEmpty())//如果沒有設定日期格式,按值長度自動設定
                        {
                            if (xnv.Length.Equals(8)) timeFormat = "yyyyMMdd";
                            if (xnv.Length.Equals(14)) timeFormat = "yyyyMMddHHmmss";
                        }
                        if (DateTime.TryParseExact(xnv, timeFormat, null, DateTimeStyles.None, out dt))
                        {
                            prop.SetValue(obj, dt, null);
                        }
                        break;
                    case DbType.Decimal:
                        Decimal dec;
                        if (Decimal.TryParse(xnv, out dec))
                        {
                            prop.SetValue(obj, dec, null);
                        }
                        break;
                    case DbType.Double:
                        double db;
                        if (Double.TryParse(xnv, out db))
                        {
                            prop.SetValue(obj, db, null);
                        }
                        break;
                    //Int32、Int16 3種類型處理方式一樣
                    case DbType.Int32:
                    case DbType.Int16:
                        int i;
                        if (int.TryParse(xnv, out i))
                        {
                            prop.SetValue(obj, i, null);
                        }
                        break;
                    case DbType.UInt64:
                        long li;
                        if (long.TryParse(xnv, out li))
                        {
                            prop.SetValue(obj, li, null);
                        }
                        break;
                    case DbType.Boolean:
                        bool b;
                        if (Boolean.TryParse(xnv, out b))
                        {
                            prop.SetValue(obj, b, null);
                        }
                        break;
                }
            }
        }

        //從prop裡取出對應的結點名,1、如果有NodeAttribute特性,則取特性裡的名稱,2、否則預設取屬性名,3、如果設定了變化規則,按規則變化
        private string GetNodeNameFromProp(PropertyInfo prop)
        {
            string name = prop.Name;//預設為屬性名
            var cus = prop.GetCustomAttributes(typeof(NodeAttribute), true);
            if (cus != null && cus.Length > 0)
            {
                string seflName = (cus[0] as NodeAttribute).Name;
                if (seflName.IsNotNullOrEmpty()) name = seflName;//註釋裡的結點名
            }
            if (nodeFormatter != null)
            {
                name = nodeFormatter(name);
            }
            return name;
        }

        /*從node的子結點或屬性中解析資料到實體中*/
        public T DeSerialize<T>(XmlNode node) where T : class
        {
            object obj = DeSerialize(typeof(T), node);
            return obj as T;
        }

        private object DeSerialize(Type type, XmlNode node)
        {
            if (node == null || type == null) return null;
            object obj = Activator.CreateInstance(type);
            var props = type.GetProperties();
            if (props == null || props.Count() <= 0) return null;
            //如果不需要處理沒有被NodeAttr標記的屬性,過濾出有NodeAttr的屬性
            if (!containPropertyWithOutNodeAttr)
            {
                props = props.Where(p =>
                {
                    var nas = p.GetCustomAttributes(typeof(NodeAttribute), true);
                    return nas != null && nas.Count() > 0;
                }).ToArray();
            }
            Entity ext = new Entity();
            //先處理基礎資料型別
            var baseProps = props.Where(p => ext.IsNormalBaseType(p));
            if (baseProps != null && baseProps.Count() > 0)
            {
                FillBaseType(node, obj, baseProps);
            }
            //處理自定義類型別
            var classProps = props.Where(p => ext.IsUserDefinedClass(p));
            if (classProps != null && classProps.Count() > 0)
            {
                foreach (var prop in classProps)
                {
                    string name = GetNodeNameFromProp(prop);
                    XmlNode xn = node.SelectSingleNode(name);
                    if (xn == null) continue;
                    object v = DeSerialize(prop.PropertyType, xn);
                    if (v != null) prop.SetValue(obj, v);
                }
            }
            return obj;
        }

        /*
         從物件屬性中取出結點名稱和結點值
         step1、如果有NodeAttribute,按順序取結點名
         step2、如果沒有NodeAttribute,取屬性名作結點名
         step3、如果設定了nodeFormatter,根據nodeFormatter更改結點名
        */
        private List<KeyValueEntity> GetAttrValue(object data)
        {
            List<KeyValueEntity> list = null;
            if (data != null)
            {
                Type type = data.GetType();
                var props = new Entity().GetNormalBaseType(type.GetProperties());
                list = new List<KeyValueEntity>();
                //有註解的屬性
                var selfNodeProps = props.Where(p =>
                {
                    var cus = p.GetCustomAttributes(typeof(NodeAttribute), true);
                    return cus != null && cus.Count() > 0;
                }).OrderBy(p =>
                {
                    NodeAttribute na = p.GetCustomAttributes(typeof(NodeAttribute), true)[0] as NodeAttribute;
                    int i = na.Index;
                    Console.WriteLine($"{na.Name},{na.Index}");
                    return i;
                }).ToArray();
                //沒有註解的屬性
                var defaultNodeProps = props.Where(p =>
                {
                    var cus = p.GetCustomAttributes(typeof(NodeAttribute), true);
                    return cus == null || cus.Count() <= 0;
                });
                List<PropertyInfo> pis = new List<PropertyInfo>();
                //按註解裡的先後順序新增結點
                if (selfNodeProps != null && selfNodeProps.Count() > 0)
                {
                    pis.AddRange(selfNodeProps);
                }
                //再新增沒有註解的結點
                if (defaultNodeProps != null && defaultNodeProps.Count() > 0)
                {
                    pis.AddRange(defaultNodeProps);
                }
                foreach (var prop in pis)
                {
                    KeyValueEntity kv = new Entities.KeyValueEntity();
                    kv.Key = prop.Name;//預設沒有註解,用屬性名
                    var attrs = prop.GetCustomAttributes(typeof(NodeAttribute), true);
                    if (attrs != null && attrs.Count() > 0)
                    {
                        NodeAttribute attr = attrs[0] as NodeAttribute;
                        if (attr.Name.IsNotNullOrEmpty())//註解裡有結點名
                        {
                            kv.Key = attr.Name;
                        }
                    }
                    if (nodeFormatter != null)//如果設定了更改規則,按規則修改
                    {
                        kv.Key = nodeFormatter(kv.Key);
                    }

                    object v = prop.GetValue(data);
                    if (prop.PropertyType.ToString().Contains("DateTime"))
                    {
                        if (v != null)
                        {
                            if (timeFormat.IsNullOrEmpty()) timeFormat = "yyyyMMdd";
                            kv.Value = Convert.ToDateTime(v).ToString(timeFormat);
                        }
                    }
                    else if (v != null)
                    {
                        kv.Value = Convert.ToString(v);
                    }
                    list.Add(kv);
                }
            }
            return list;
        }
    }

    /// <summary>
    /// xml文件序列化,反序列化
    /// TimeFormat:設定日期屬性的格式
    /// NodeNameFormatter:可以對結點名稱格式化
    /// 結點名:如果設定了NodeAttr的Name,按設定取,否則預設取屬性名
    /// CreateNodeWhenNull:設定屬性值為null時是否生成結點
    /// ContainPropertyWithOutNodeAttr:屬性值沒有被NodeAttr標記時,是否生成、解析結點
    /// </summary>
    public class XmlSerializer : XmlNodeSerializer
    {
        public new XmlDocument Serialize<T>(T data)
        {
            return Serialize(data, "UTF-8");
        }

        public XmlDocument Serialize<T>(T data, string encoding)
        {
            if (data == null) return null;
            XmlDocument doc = new XmlDocument();
            XmlDeclaration dec = doc.CreateXmlDeclaration("1.0", encoding, null);
            doc.AppendChild(dec);
            XmlNode node = Serialize(data, doc);
            if (node != null)
            {
                doc.AppendChild(node);
            }
            return doc;
        }

        public T DeSerialize<T>(XmlDocument doc) where T : class
        {
            if (doc == null) return null;
            Type type = typeof(T);
            string nodeName = string.Empty; //結點名
            NodeAttribute na = type.GetCustomAttribute<NodeAttribute>();
            if (na == null || na.Name.IsNullOrEmpty())
            {
                nodeName = type.Name;//如果沒有設定,使用類名
            }
            else
            {
                nodeName = na.Name;//取設定的結點名
            }
            if (nodeFormatter != null) nodeName = nodeFormatter(nodeName);
            XmlNode node = doc.SelectSingleNode(nodeName);
            if (node == null) return null;
            return DeSerialize<T>(node); //doc.firstchild可能是XmlDeclaration
        }

        public T DeSerialize<T>(string xml) where T : class
        {
            try
            {
                XmlDocument doc = new XmlDocument();
                doc.LoadXml(xml);
                return DeSerialize<T>(doc);
            }
            catch (XmlException ex)
            {
                Console.WriteLine(ex.Message);
                return null;
            }
        }
    }

    public class NodeAttribute : Attribute
    {
        /// <summary>
        /// 結點名稱
        /// </summary>
        public string Name { get; set; }

        /// <summary>
        /// 結點在文件內的順序
        /// </summary>
        public int Index { get; set; } = int.MaxValue;//預設最大值,因為有些node只設置了name沒設定index,index就會預設為0就排到第一個去了
    }