Имя элемента элемента списка XmlSerializer - PullRequest
34 голосов
/ 19 февраля 2010

У меня есть класс PersonList

[XmlRoot("Persons")]
PersonList : List<Human>

, когда я сериализирую это в XML, по умолчанию он выдаст что-то вроде этого:

<Persons>
  <Human>...</Human>
  <Human>...</Human>
</Persons>

Мой вопрос заключается в том, что нужнобыть сделано для того, чтобы изменить элемент Human на Person в выводе?поэтому вывод будет:

<Persons>
  <Person>...</Person>
  <Person>...</Person>
</Persons>

и как десериализовать вышеуказанный XML в объект класса PersonList?

Согласно совету Ника, вот мой код тестирования:

[XmlRoot("Persons")]
public class Persons : List<Human>
{

}

[XmlRoot("Person")]
public class Human
{
    public Human()
    {
    }

    public Human(string name)
    {
        Name = name;
    }

    [XmlElement("Name")]
    public string Name { get; set; }

}

void TestXmlSerialize()
{
    Persons personList = new Persons();
    personList.Add(new Human("John"));
    personList.Add(new Human("Peter"));

    try
    {
        using (StringWriter writer = new StringWriter())
        {
            XmlSerializer serializer = new XmlSerializer(typeof(Persons));
            XmlWriterSettings settings = new XmlWriterSettings();
            settings.OmitXmlDeclaration = true;

            XmlSerializerNamespaces namespaces = new XmlSerializerNamespaces();
            namespaces.Add(string.Empty, string.Empty);

            XmlWriter xmlWriter = XmlWriter.Create(writer, settings);
            serializer.Serialize(xmlWriter, personList, namespaces);

            Console.Out.WriteLine(writer.ToString());
        }
    }
    catch (Exception e)
    {
        Console.Out.WriteLine( e.ToString());
    }
}

Вывод тестового кода:

<Persons>
  <Human>
    <Name>John</Name>
  </Human>
  <Human>
    <Name>Peter</Name>
  </Human>
</Persons>

Как показывает вывод, [XmlRoot("Person")] на Human не меняет тег на Person с Human.

Ответы [ 8 ]

54 голосов
/ 18 октября 2011

Отметьте свой класс следующими атрибутами:

[XmlType("Account")]
[XmlRoot("Account")]
29 голосов
/ 19 февраля 2010

Я не думаю, что у вас есть способ контролировать имя сгенерированных элементов массива.

Если вы, однако, можете обернуть коллекцию Persons в другой класс, вы получите полный контроль надсгенерированный вывод с использованием XmlArrayAttribute и XmlArrayItemAttribute.

Если вы не можете создать этот новый класс, вы можете прибегнуть к реализации IXmlSerializable, но это намного сложнее.

Пример дляследует первая альтернатива:

[XmlRoot("Context")]
public class Context
{
    public Context() { this.Persons = new Persons(); }

    [XmlArray("Persons")]
    [XmlArrayItem("Person")]
    public Persons Persons { get; set; }
}

public class Persons : List<Human> { }

public class Human
{
    public Human() { }
    public Human(string name) { Name = name; }
    public string Name { get; set; }
}

class Program
{
    public static void Main(string[] args)
    {
        Context ctx = new Context();
        ctx.Persons.Add(new Human("john"));
        ctx.Persons.Add(new Human("jane"));

        var writer = new StringWriter();
        new XmlSerializer(typeof(Context)).Serialize(writer, ctx);

        Console.WriteLine(writer.ToString());
    }
}
13 голосов
/ 08 апреля 2010

У меня была идентичная проблема с моим сериализатором. Ни один из ответов выше не сработал точно. Я обнаружил, что атрибут XmlRoot в классе Human явно игнорируется, поскольку он не является корневым элементом документа. Завершение списка в объект контекста не было для меня вариантом, потому что я не могу изменить схему XML. Решение состоит в том, чтобы изменить класс Persons. Вместо того, чтобы создавать подклассы общего списка, вы помещаете его в объект и изменяете способ его сериализации. Смотрите пример кода ниже:

[XmlRoot("Persons")]
public class Persons 
{
    public Persons ()
    {
        People = new List<Human>();
    }

    [XmlElement("Person")]
    public List<Human> People 
    { get; set; }
}

public class Human
{
    public Human()
    {
    }

    public Human(string name)
    {
        Name = name;
    }

    [XmlElement("Name")]
    public string Name { get; set; }
}

Сериализация вашего общего списка с использованием XmlElement означает, что он не будет помещать элемент-оболочку вокруг вашего списка, как это делает XmlArray или как это делает подкласс. Это также дает вам бонусную возможность добавления атрибутов в класс Persons, откуда я и пришла идея:

Как добавить атрибут в элемент XmlArray (XML-сериализация)?

4 голосов
/ 10 июля 2012

Это мой тестовый код

using System.Collections.Generic;
using System.Xml.Serialization;

namespace TestLoadingMultiXml
{
[XmlRoot(ElementName=@"main")]
public class XmlMain
{
    private XmlDataTest data;

    [XmlElement(ElementName=@"datalist")]
    public XmlDataTest Data
    {
        get { return data; }
        set { data = value; }
    } // public XmlDataTest Data

    public XmlMain()
    {
        data = new XmlDataTest();
    }
}

[XmlRoot(ElementName=@"xmldata")]
public class XmlDataTest
{
    private List<DataDetails> listData;

    [XmlElement(ElementName=@"listdata")]
    public List<DataDetails> Data
    {
        get { return listData; }
        set { listData = value; }
    }

    public XmlDataTest()
    {
        listData = new List<DataDetails>();
        for (int i = 0; i < 10; i++)
        {
            DataDetails d = new DataDetails(string.Format("{0}", i));
            listData.Add(d);
        } // for (int i=0; i < 10; i++)
    } // public XmlDataTest()
} // class XmlDataTest

[XmlRoot(ElementName=@"datadetail")]
public class DataDetails
{
    private string name;

    [XmlAttribute(AttributeName=@"name")]
    public string Name
    {
        get
        {
            return name;
        }
        set { name = value; }
    }

    public DataDetails(string _value)
    {
        this.name = _value;
    } // public DataDetails(string _value)

    public DataDetails()
    {
        this.name = "";
    } // public DataDetails()
} // public class DataDetails
}

и запущенная программа

using System;
using System.IO;
using System.Windows.Forms;
using System.Xml.Serialization;

namespace TestLoadingMultiXml
{
    public partial class Form1 : Form
    {
    private XmlMain xt;
    private string xname = @"x.xml";

    public Form1()
    {
        InitializeComponent();
        this.FormClosing += new FormClosingEventHandler(Form1_FormClosing);
    }

    void Form1_FormClosing(object sender, FormClosingEventArgs e)
    {
        XmlSerializer x = new XmlSerializer(typeof(XmlMain));
        FileStream fs = new FileStream(xname, FileMode.Create);
        x.Serialize(fs, xt);
        fs.Close();
    }

    private void Form1_Load(object sender, EventArgs e)
    {
        xt = new XmlMain();
        xname = Directory.GetCurrentDirectory() + @"\" + xname;
        if (File.Exists(xname))
        {
            XmlSerializer x = new XmlSerializer(typeof(XmlMain));
            FileStream fs = new FileStream(xname, FileMode.Open);
            xt = (XmlMain)x.Deserialize(fs);
            fs.Close();
        } // if (File.Exists(xname))
    }
}
}

Также это результат

<?xml version="1.0"?>
<main xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"     xmlns:xsd="http://www.w3.org/2001/XMLSchema">
  <datalist>
    <listdata name="0" />
    <listdata name="1" />
    <listdata name="2" />
    <listdata name="3" />
    <listdata name="4" />
    <listdata name="5" />
    <listdata name="6" />
    <listdata name="7" />
    <listdata name="8" />
    <listdata name="9" />
  </datalist>
</main>
3 голосов
/ 19 февраля 2010

Установите XmlRoot для человека на:

[XmlRoot("Person")]

Боковая панель:

Персоны должны быть, вероятно, Люди

2 голосов
/ 28 мая 2014

Есть еще одна альтернатива. Вы всегда можете реализовать IXmlSerializable в коллекциях (и любом другом классе или структуре), чтобы полностью контролировать, как элементы пишутся или читаются. Многие из вас уже знают, что, как правило, это не является предпочтительным, поскольку в конечном итоге вы можете вручную написать «стандартный код», который на самом деле должен быть автоматической логикой, заданной с помощью атрибутов.

Для коллекций, которые должны соответствовать разумной схеме, это оправдано. Потому что здесь есть жесткое ограничение каркаса, и код сериализации существующего типа элемента не должен дублироваться при правильном выполнении; Т.е. не переписывайте сериализацию элемента в коде коллекции, просто создайте / вызовите дочерний XmlSerializer внутри вашей реализации ReadXml / WriteXml.

Как только следствие использования IXmlSerializable не позволяет вам применять XmlTypeAttribute (выдает ошибку времени выполнения, сообщающую, что можно использовать только XmlRootAttribute). Поэтому вместо этого примените XmlSchemaProviderAttribute и верните то же квалифицированное имя, которое вы бы поместили в XmlTypeAttribute. Старый метод GetSchema должен в любом случае возвращать значение null, поскольку это был только зарезервированный метод (в соответствии с MSDN), возможно, потому что они забыли включить возможность указать другое пространство имен. Лично я использую то же имя метода «GetSchema» в своем атрибуте XmlSchemaProviderAttribute, поэтому оно отображается как полное переопределение рядом с устаревшим методом-заполнителем GetSchema.

Конечно, лучшее решение было бы, если бы Microsoft разрешила нам применить XmlArrayItemAttribute к классам коллекции / списка и использовать его в XmlSerializer. По умолчанию он использует имя элемента типа XML в коллекциях, что, по моему мнению, является ошибкой, поскольку оно должно быть корневым именем XML, если оно указано, или именем класса, если нет.

Когда у меня будет время, я вернусь и добавлю пример. А пока взглянем на примеры документации по MSDN IXmlSerializable и XmlSchemaProviderAttribute.

http://msdn.microsoft.com/en-us/library/system.xml.serialization.ixmlserializable(v=vs.110).aspx

http://msdn.microsoft.com/en-us/library/system.xml.serialization.xmlschemaproviderattribute(v=vs.110).aspx

1 голос
/ 19 февраля 2010

Если у вас нет доступа к источнику для класса Human (в этом случае настройка XmlRoot невозможна), вы можете создать XmlElementAttribute, затем добавить его в XmlAttributeOverride и использовать его при создании экземпляра вашего XmlSerializer. См. Эту статью MSDN для более подробной информации .

0 голосов
/ 07 октября 2016

Я знаю, что это старый вопрос, но я столкнулся с той же проблемой, и ни одно из решений, похоже, не отвечает на вопрос ОП. Итак, вот мое решение (комментарии на французском, если вам интересно):

#region Références
using System.Collections.Generic;
using System.Linq;
using System.Xml;
using System.Xml.Schema;
using System.Xml.Serialization;
#endregion

namespace XmlSerializationTests
{
    /// <summary>
    /// Représente une liste qui peut être sérialisée en XML en tant que noeud racine.
    /// </summary>
    /// <typeparam name="T">Type des éléments de la liste.</typeparam>
    public class XmlSerializableList<T>
        : List<T>, IXmlSerializable
    {
        #region Variables
        private static readonly XmlSerializer _ItemSerializer = new XmlSerializer(typeof(T));
        private static readonly string _ItemName;
        private string _RootName;
        #endregion

        #region Méthodes
        /// <summary>
        /// Initialisation statique
        /// </summary>
        static XmlSerializableList()
        {
            _ItemName = (typeof(T).GetCustomAttributes(typeof(XmlRootAttribute), true).FirstOrDefault() as XmlRootAttribute)?.ElementName ?? typeof(T).Name;
        }

        /// <summary>
        /// Obtient le nom racine.
        /// </summary>
        protected virtual string RootName
        {
            get
            {
                if (string.IsNullOrWhiteSpace(_RootName)) _RootName = (GetType().GetCustomAttributes(typeof(XmlRootAttribute), true).FirstOrDefault() as XmlRootAttribute)?.ElementName ?? GetType().Name;
                return _RootName;
            }
        }

        /// <summary>
        /// Obtient le nom des éléments.
        /// </summary>
        protected virtual string ItemName
        {
            get { return _ItemName; }
        }

        /// <summary>
        /// Cette méthode est réservée et ne doit pas être utilisée.Lorsque vous implémentez l'interface IXmlSerializable, vous devez retourner la valeur null (Nothing dans Visual Basic) à partir cette méthode et, si la spécification d'un schéma personnalisé est requise, appliquez à la place <see cref="T:System.Xml.Serialization.XmlSchemaProviderAttribute"/> à la classe.
        /// </summary>
        /// <returns> <see cref="T:System.Xml.Schema.XmlSchema"/> qui décrit la représentation XML de l'objet qui est généré par la méthode <see cref="M:System.Xml.Serialization.IXmlSerializable.WriteXml(System.Xml.XmlWriter)"/> et utilisé par la méthode <see cref="M:System.Xml.Serialization.IXmlSerializable.ReadXml(System.Xml.XmlReader)"/>.</returns>
        public XmlSchema GetSchema()
        {
            return null;
        }

        /// <summary>
        /// Génère un objet à partir de sa représentation XML.
        /// </summary>
        /// <param name="reader"><see cref="T:System.Xml.XmlReader"/> source à partir de laquelle l'objet est désérialisé.</param>
        public void ReadXml(XmlReader reader)
        {
            if (!reader.IsEmptyElement)
            {
                reader.ReadStartElement();
                while (reader.NodeType != XmlNodeType.EndElement)
                {
                    T item = (T) _ItemSerializer.Deserialize(reader);
                    Add(item);
                }
                reader.ReadEndElement();
            }
            else reader.ReadStartElement();
        }

        /// <summary>
        /// Convertit un objet en sa représentation XML.
        /// </summary>
        /// <param name="writer"><see cref="T:System.Xml.XmlWriter"/> flux dans lequel l'objet est sérialisé.</param>
        public void WriteXml(XmlWriter writer)
        {
            foreach (var i in this)
            {
                XmlSerializerNamespaces ns = new XmlSerializerNamespaces();
                ns.Add("", "");
                _ItemSerializer.Serialize(writer, i, ns);
            }
        }
        #endregion
    }
}

А вот класс юнит-теста для демонстрации использования и результатов:

#region Références
using System.IO;
using System.Text;
using System.Xml.Serialization;
using Microsoft.VisualStudio.TestTools.UnitTesting;
#endregion

namespace XmlSerializationTests
{
    [TestClass]
    public class XmlSerializableListTests
    {
        public class Person
        {
            public string FirstName { get; set; }
            public string LastName { get; set; }
            public int Birth { get; set; }
        }

        [XmlRoot("color")]
        public class ColorDefinition
        {
            [XmlElement("name")] public string Name { get; set; }
            [XmlElement("r")] public int Red { get; set; }
            [XmlElement("g")] public int Green { get; set; }
            [XmlElement("b")] public int Blue { get; set; }
        }

        public class Persons : XmlSerializableList<Person>
        {
        }

        [XmlRoot("colors")]
        public class ColorList : XmlSerializableList<ColorDefinition>
        {
        }

        private T ReadXml<T>(string text) where T : class
        {
            XmlSerializer serializer = new XmlSerializer(typeof (T));
            using (StringReader sr = new StringReader(text))
            {
                return serializer.Deserialize(sr) as T;
            }
        }

        private string WriteXml<T>(T data) where T : class
        {
            XmlSerializer serializer = new XmlSerializer(typeof(T));
            StringBuilder sb = new StringBuilder();
            using (StringWriter sw = new StringWriter(sb))
            {
                serializer.Serialize(sw, data);
                return sb.ToString();
            }
        }

        [TestMethod]
        public void ReadEmpty()
        {
            string xml = @"<?xml version=""1.0"" encoding=""utf-16""?>
<XmlSerializableListOfInt32>
</XmlSerializableListOfInt32>";
            XmlSerializableList<int> lst = ReadXml<XmlSerializableList<int>>(xml);
            Assert.AreEqual(0, lst.Count);
        }

        [TestMethod]
        public void ReadEmpty2()
        {
            string xml = @"<?xml version=""1.0"" encoding=""utf-16""?>
<XmlSerializableListOfInt32 />";
            XmlSerializableList<int> lst = ReadXml<XmlSerializableList<int>>(xml);
            Assert.AreEqual(0, lst.Count);
        }

        [TestMethod]
        public void ReadSimpleItems()
        {
            string xml = @"<?xml version=""1.0"" encoding=""utf-16""?>
<XmlSerializableListOfInt32>
  <int>0</int>
  <int>52</int>
  <int>79</int>
</XmlSerializableListOfInt32>";
            XmlSerializableList<int> lst = ReadXml<XmlSerializableList<int>>(xml);
            Assert.AreEqual(3, lst.Count);
            Assert.AreEqual(0, lst[0]);
            Assert.AreEqual(52, lst[1]);
            Assert.AreEqual(79, lst[2]);
        }

        [TestMethod]
        public void ReadComplexItems()
        {
            string xml = @"<?xml version=""1.0"" encoding=""utf-16""?>
<XmlSerializableListOfPerson>
  <Person>
    <FirstName>Linus</FirstName>
    <LastName>Torvalds</LastName>
    <Birth>1969</Birth>
  </Person>
  <Person>
    <FirstName>Bill</FirstName>
    <LastName>Gates</LastName>
    <Birth>1955</Birth>
  </Person>
  <Person>
    <FirstName>Steve</FirstName>
    <LastName>Jobs</LastName>
    <Birth>1955</Birth>
  </Person>
</XmlSerializableListOfPerson>";
            XmlSerializableList<Person> lst = ReadXml<XmlSerializableList<Person>>(xml);
            Assert.AreEqual(3, lst.Count);
            Assert.AreEqual("Linus", lst[0].FirstName);
            Assert.AreEqual("Torvalds", lst[0].LastName);
            Assert.AreEqual(1969, lst[0].Birth);
            Assert.AreEqual("Bill", lst[1].FirstName);
            Assert.AreEqual("Gates", lst[1].LastName);
            Assert.AreEqual(1955, lst[1].Birth);
            Assert.AreEqual("Steve", lst[2].FirstName);
            Assert.AreEqual("Jobs", lst[2].LastName);
            Assert.AreEqual(1955, lst[2].Birth);
        }

        [TestMethod]
        public void ReadInheritedPersons()
        {
            string xml = @"<?xml version=""1.0"" encoding=""utf-16""?>
<Persons>
  <Person>
    <FirstName>Linus</FirstName>
    <LastName>Torvalds</LastName>
    <Birth>1969</Birth>
  </Person>
  <Person>
    <FirstName>Bill</FirstName>
    <LastName>Gates</LastName>
    <Birth>1955</Birth>
  </Person>
  <Person>
    <FirstName>Steve</FirstName>
    <LastName>Jobs</LastName>
    <Birth>1955</Birth>
  </Person>
</Persons>";
            Persons lst = ReadXml<Persons>(xml);
            Assert.AreEqual(3, lst.Count);
            Assert.AreEqual("Linus", lst[0].FirstName);
            Assert.AreEqual("Torvalds", lst[0].LastName);
            Assert.AreEqual(1969, lst[0].Birth);
            Assert.AreEqual("Bill", lst[1].FirstName);
            Assert.AreEqual("Gates", lst[1].LastName);
            Assert.AreEqual(1955, lst[1].Birth);
            Assert.AreEqual("Steve", lst[2].FirstName);
            Assert.AreEqual("Jobs", lst[2].LastName);
            Assert.AreEqual(1955, lst[2].Birth);
        }

        [TestMethod]
        public void ReadInheritedColors()
        {
            string xml = @"<?xml version=""1.0"" encoding=""utf-16""?>
<colors>
  <color>
    <name>red</name>
    <r>255</r>
    <g>0</g>
    <b>0</b>
  </color>
  <color>
    <name>green</name>
    <r>0</r>
    <g>255</g>
    <b>0</b>
  </color>
  <color>
    <name>yellow</name>
    <r>255</r>
    <g>255</g>
    <b>0</b>
  </color>
</colors>";
            ColorList lst = ReadXml<ColorList>(xml);
            Assert.AreEqual(3, lst.Count);
            Assert.AreEqual("red", lst[0].Name);
            Assert.AreEqual(255, lst[0].Red);
            Assert.AreEqual(0, lst[0].Green);
            Assert.AreEqual(0, lst[0].Blue);
            Assert.AreEqual("green", lst[1].Name);
            Assert.AreEqual(0, lst[1].Red);
            Assert.AreEqual(255, lst[1].Green);
            Assert.AreEqual(0, lst[1].Blue);
            Assert.AreEqual("yellow", lst[2].Name);
            Assert.AreEqual(255, lst[2].Red);
            Assert.AreEqual(255, lst[2].Green);
            Assert.AreEqual(0, lst[2].Blue);
        }

        [TestMethod]
        public void WriteEmpty()
        {
            string xml = @"<?xml version=""1.0"" encoding=""utf-16""?>
<XmlSerializableListOfInt32 />";
            XmlSerializableList<int> lst = new XmlSerializableList<int>();
            string result = WriteXml(lst);
            Assert.AreEqual(xml, result);
        }

        [TestMethod]
        public void WriteSimpleItems()
        {
            string xml = @"<?xml version=""1.0"" encoding=""utf-16""?>
<XmlSerializableListOfInt32>
  <int>0</int>
  <int>52</int>
  <int>79</int>
</XmlSerializableListOfInt32>";
            XmlSerializableList<int> lst = new XmlSerializableList<int>() {0, 52, 79};
            string result = WriteXml(lst);
            Assert.AreEqual(xml, result);
        }

        [TestMethod]
        public void WriteComplexItems()
        {
            string xml = @"<?xml version=""1.0"" encoding=""utf-16""?>
<XmlSerializableListOfPerson>
  <Person>
    <FirstName>Linus</FirstName>
    <LastName>Torvalds</LastName>
    <Birth>1969</Birth>
  </Person>
  <Person>
    <FirstName>Bill</FirstName>
    <LastName>Gates</LastName>
    <Birth>1955</Birth>
  </Person>
  <Person>
    <FirstName>Steve</FirstName>
    <LastName>Jobs</LastName>
    <Birth>1955</Birth>
  </Person>
</XmlSerializableListOfPerson>";
            XmlSerializableList<Person> persons = new XmlSerializableList<Person>
            {
                new Person {FirstName = "Linus", LastName = "Torvalds", Birth = 1969},
                new Person {FirstName = "Bill", LastName = "Gates", Birth = 1955},
                new Person {FirstName = "Steve", LastName = "Jobs", Birth = 1955}
            };
            string result = WriteXml(persons);
            Assert.AreEqual(xml, result);
        }

        [TestMethod]
        public void WriteInheritedPersons()
        {
            string xml = @"<?xml version=""1.0"" encoding=""utf-16""?>
<Persons>
  <Person>
    <FirstName>Linus</FirstName>
    <LastName>Torvalds</LastName>
    <Birth>1969</Birth>
  </Person>
  <Person>
    <FirstName>Bill</FirstName>
    <LastName>Gates</LastName>
    <Birth>1955</Birth>
  </Person>
  <Person>
    <FirstName>Steve</FirstName>
    <LastName>Jobs</LastName>
    <Birth>1955</Birth>
  </Person>
</Persons>";
            Persons lst = new Persons
            {
                new Person {FirstName = "Linus", LastName = "Torvalds", Birth = 1969},
                new Person {FirstName = "Bill", LastName = "Gates", Birth = 1955},
                new Person {FirstName = "Steve", LastName = "Jobs", Birth = 1955}
            };
            string result = WriteXml(lst);
            Assert.AreEqual(xml, result);
        }

        [TestMethod]
        public void WriteInheritedColors()
        {
            string xml = @"<?xml version=""1.0"" encoding=""utf-16""?>
<colors>
  <color>
    <name>red</name>
    <r>255</r>
    <g>0</g>
    <b>0</b>
  </color>
  <color>
    <name>green</name>
    <r>0</r>
    <g>255</g>
    <b>0</b>
  </color>
  <color>
    <name>yellow</name>
    <r>255</r>
    <g>255</g>
    <b>0</b>
  </color>
</colors>";
            ColorList lst = new ColorList
            {
                new ColorDefinition { Name = "red", Red = 255, Green = 0, Blue = 0 },
                new ColorDefinition { Name = "green", Red = 0, Green = 255, Blue = 0 },
                new ColorDefinition { Name = "yellow", Red = 255, Green = 255, Blue = 0 }
            };
            string result = WriteXml(lst);
            Assert.AreEqual(xml, result);
        }
    }
}
...