Есть ли способ сохранить атрибуты XML при сериализации части класса?


Я пытаюсь сериализовать только часть класса. Я добавил атрибуты XML к членам класса, чтобы сгенерированные теги XML были правильно названы в соответствии со спецификацией, независимо от того, как называются Мои свойства. Это прекрасно работает при сериализации основного класса. Однако, если я просто хочу сериализовать часть класса, я теряю атрибуты XML, и имена возвращаются к их значениям по умолчанию. Есть ли способ сохранить атрибуты XML при сериализации только части класс?

[XmlRoot ("someConfiguration")]
public class SomeConfiguration
{
    [XmlArray("bugs")]
    [XmlArrayItem("bug")]
    public List<string> Bugs { get; set; }
}

Когда я сериализую весь класс, я получаю это (что в точности соответствует моим ожиданиям):

<someConfiguration>
  <bugs>
    <bug>Bug1</bug>
    <bug>Bug2</bug>
    <bug>Bug3</bug>
  </bugs>
</someConfiguration>

Если я попытаюсь просто сериализовать часть класса "ошибки", я получу следующее (обратите внимание, что атрибуты XML, изменяющие имена тегов, игнорируются):

<ArrayOfString>
  <string>Bug1</string>
  <string>Bug2</string>
  <string>Bug3</string>
</ArrayOfString>

Мне нужно получить это:

  <bugs>
    <bug>Bug1</bug>
    <bug>Bug2</bug>
    <bug>Bug3</bug>
  </bugs>

Как заставить частичный класс сериализоваться с помощью указанных выше тегов?

Или еще лучше, есть ли способ указать имена тегов при сериализации простого List<object>. Так что ты можно ли указать тег, используемый для списка вместо него с помощью <ArrayOfobject> , и указать тег, используемый для элементов массива вместо <object>?

3 3

3 ответа:

Есть ли способ указать имена тегов при сериализации простого списка.

В общем, в зависимости от точного сценария, возможно, удастся заставить это работать. Смотрите раздел MSDN Как указать альтернативное имя элемента для XML-потока . Этот пример включает в себя переопределение сериализации определенного поля,но может быть возможно использовать тот же метод для переопределения целых имен типов.

Но мне кажется, что это ужасно много хлопот. Вместо, почему бы просто не обработать сериализацию явно:
private static string SerializeByLinqAndToString<T>(
    List<T> data, string rootName, string elementName)
{
    XDocument document = new XDocument(
        new XElement(rootName, data.Select(s => new XElement(elementName, s))));

    return SaveXmlToString(document);
}

private static string SaveXmlToString(XDocument document)
{
    StringBuilder sb = new StringBuilder();

    using (XmlWriter xmlWriter = XmlWriter.Create(sb,
        new XmlWriterSettings { Indent = true, OmitXmlDeclaration = true }))
    {
        document.Save(xmlWriter);
    }

    return sb.ToString();
}

Звоните так:

SomeConfiguration config = ...; // initialize as desired

string result = SerializeByLinq(config.Bugs, "bug", "bugs");
Вышеописанное работает только со списком строк или списком типов, где содержимое элемента может быть просто результатом вызова ToString() на экземпляре типа.

Использование полномасштабных функций сериализации в .NET может быть полезным при работе со сложными типами, но если все, что у вас есть,-это простой список строк, функция LINQ-to-XML очень удобна.

Если у вас есть более сложные типы, вы можете преобразовать каждый элемент списка в XElement для DOM и сериализовать это:

private static string SerializeByLinq<T>(
    List<T> data, string rootName, string elementName = null)
{
    XDocument document = new XDocument(
        new XElement(rootName, data.Select(t =>
            ElementFromText(SerializeObject(t), elementName)
        )));

    return SaveXmlToString(document);
}

private static XElement ElementFromText(string xml, string name = null)
{
    StringReader reader = new StringReader(xml);
    XElement result = XElement.Load(reader);

    if (!string.IsNullOrEmpty(name))
    {
        result.Name = name;
    }

    return result;
}

private static string SerializeObject<T>(T o)
{
    XmlSerializer xmlSerializer = new XmlSerializer(typeof(T));
    StringWriter textWriter = new StringWriter();

    using (XmlWriter writer = XmlWriter.Create(textWriter,
        new XmlWriterSettings { Indent = true, OmitXmlDeclaration = true }))
    {
        xmlSerializer.Serialize(writer, o,
            new XmlSerializerNamespaces(new[] { XmlQualifiedName.Empty}));
    }

    return textWriter.ToString();
}

В этом втором примере вы можете опустить имя элемента для дочернего элемента, и он будет просто использовать то, что уже настроено для использования типом (например, имя типа или то, что установлено в [XmlRoot]).

Просто выбросив это, вы можете обернуть список внутри пользовательского класса:

[XmlRoot("config")]
public class SomeConfiguration
{
    [XmlElement("bugs")]
    public BugList Bugs { get; set; }
    [XmlElement("trees")]
    public TreeList Trees { get; set; }
}

[XmlRoot("bugs")]
public class BugList 
{
    [XmlElement("bug")]
    public List<string> Items = new List<string>();
}

[XmlRoot("trees")]
public class TreeList
{
    [XmlElement("tree")]
    public List<string> Items = new List<string>();
}   
Это теперь позволит вам сериализовать отдельные списки, и они будут укоренены, как вы и ожидали.
void Main()
{
    var config = new SomeConfiguration
    {
        Bugs = new BugList { Items = { "Bug1", "Bug2" } },
        Trees = new TreeList { Items = { "Tree1", "Tree2" } }
    };

    // Your config will work as normal.
    Debug.WriteLine(ToXml(config)); // <config> <bugs>.. <trees>..</config>

    // Your collections are now root-ed properly.
    Debug.WriteLine(ToXml(config.Bugs)); // <bugs><bug>Bug1</bug><bug>Bug2</bug></bugs>
    Debug.WriteLine(ToXml(config.Trees)); // <trees><tree>Tree1</tree><tree>Tree2</tree></trees>
}

public string ToXml<T>(T obj)
{
    var ser = new XmlSerializer(typeof(T));
    var emptyNs = new XmlSerializerNamespaces();
    emptyNs.Add("","");
    using (var stream = new MemoryStream())
    {
        ser.Serialize(stream, obj, emptyNs);
        return Encoding.ASCII.GetString(stream.ToArray());
    }
}

Нашел обходной способ сделать это.. Вместо того, чтобы помещать атрибуты XMLArray и XMLArrayList выше списка:

[XmlRoot ("someConfiguration")]
public class SomeConfiguration
{
    [XmlArray("bugs")]
    [XmlArrayItem("bug")]
    public List<string> Bugs { get; set; }
}

Поместите атрибут XmlElement в список, который будет указывать тег, используемый для каждого элемента, и не будет иметь тега, обертывающего список. Ваш тег класса фактически сделает это за вас.

[XmlRoot ("bugs")]
public class SomeConfiguration
{
    [XmlElement("bug")]
    public List<string> Bugs { get; set; }
}

Когда вы сериализуете вышеизложенное, вы получите:

  <bugs>
    <bug>Bug1</bug>
    <bug>Bug2</bug>
    <bug>Bug3</bug>
  </bugs>