Сериализация произвольного класса в XML

JohnSparrow
Дата: 28.05.2014 01:00:48
Доброго времени суток

Предполагаю для сохранения настроек использовать XML-файлы. Классы настроек могут быть достаточно сложными (с коллекциями и коллекциями в коллекциях, например), причем компонент приложения, который будет заниматься их сериализацией/десериализацией, заранее об этих классах не знает. Имеется ли готовое решение для подобных случаев?
Сон Веры Павловны
Дата: 28.05.2014 06:38:17
Стандартный XmlSerializer:
public class Foo
{
  public List<Bar> Bars { get; set; }
  public string Name { get; set; }
}

public class Bar
{
  public List<Zot> Zots { get; set; }
  public string Name { get; set; }
}

public class Zot
{
  public string Name { get; set; }
}
....
var f = new Foo
{
  Name = "Foo",
  Bars = new List<Bar>
    {
      new Bar
        {
          Name = "Bar1",
          Zots = new List<Zot> {new Zot {Name = "Zot11"}, new Zot {Name = "Zot12"}}
        },
      new Bar
        {
          Name = "Bar2",
          Zots = new List<Zot> {new Zot {Name = "Zot21"}, new Zot {Name = "Zot22"}}
        }
    }
};
var serializer = new XmlSerializer(typeof (Foo));
var sb = new StringBuilder();
using(var sw = new StringWriter(sb))
using(var xw = XmlWriter.Create(sw, new XmlWriterSettings{Indent = true, IndentChars = "  "}))
  serializer.Serialize(xw, f);
Console.WriteLine(sb);

Результат:
<Foo xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
  <Bars>
    <Bar>
      <Zots>
        <Zot>
          <Name>Zot11</Name>
        </Zot>
        <Zot>
          <Name>Zot12</Name>
        </Zot>
      </Zots>
      <Name>Bar1</Name>
    </Bar>
    <Bar>
      <Zots>
        <Zot>
          <Name>Zot21</Name>
        </Zot>
        <Zot>
          <Name>Zot22</Name>
        </Zot>
      </Zots>
      <Name>Bar2</Name>
    </Bar>
  </Bars>
  <Name>Foo</Name>
</Foo>

Для более тонкой настройки сериализации используются атрибуты.
Только есть пара нюансов:
1. В случае вот такого варианта:
public class Foo
{
  public IEnumerable<Bar> Bars { get; set; }
  public string Name { get; set; }
}
....
var f = new Foo
{
  Name = "Foo",
  Bars = new List<Bar>
    {
      new Bar
        {
          Name = "Bar1",
          Zots = new List<Zot> {new Zot {Name = "Zot11"}, new Zot {Name = "Zot12"}}
        },
      new Bar
        {
          Name = "Bar2",
          Zots = new List<Zot> {new Zot {Name = "Zot21"}, new Zot {Name = "Zot22"}}
        }
    }.Select(b=>b)
};

конструктор XmlSerializer'а выкинет исключение, у которого внутри будет
{"Cannot serialize member FooLib.Foo.Bars of type System.Collections.Generic.IEnumerable`1[[FooLib.Bar, FooLib, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null]] because it is an interface."}
Т.е. отдаваемые наружу коллекции должны быть классами.
2. XML-сериализация очень тормозная. Я лично вместо нее использую JSON-сериализацию (в т.ч. для настроек) c помощью JSON.Net - получается заметно быстрее XML. Хотя и здесь есть свои нюансы.
JohnSparrow
Дата: 28.05.2014 15:17:56
Сон Веры Павловны,
большое Вам спасибо, буду пробовать.
Roman Mejtes
Дата: 28.05.2014 15:59:08
JohnSparrow, есть еще геморои с наследованием классов )
JohnSparrow
Дата: 28.05.2014 17:59:11
Попробовал Json.NET. Сериализируется все, вроде бы, отлично, но с десериализацией проблемы.
Либо нужно указывать тип, в экземпляр которого будут десериализироваться данные (JsonConvert.DeserializeObject<Type>(json text)), либо (JsonConvert.DeserializeObject(json text))в качестве результата будет получен объект типа Newtonsoft.Json.Linq.JObject, доступ к свойствам которого осуществляется через Newtonsoft.Json.Linq.JObject.Properties().

В общем, хотелось бы, чтобы результат десериализации можно было преобразовывать в объект нужного типа. Это возможно?

Вот пример написанного выше:
Roman Mejtes
Дата: 28.05.2014 18:04:22
JohnSparrow,

автор
Либо нужно указывать тип, в экземпляр которого будут десериализироваться данные
а в стандартном XML сериализаторе разве не нужно указывать тип в который будет xml десериализовываться? :)
JohnSparrow
Дата: 28.05.2014 18:40:14
Нужно. И этим он мне не нравится.

Есть один вариант - http://www.codeproject.com/Articles/15646/A-Deep-XmlSerializer-Supporting-Complex-Classes-En
но у него свои проблемы: не сериализуется Guid, а также Dictionary<string, object>; наверняка есть еще нюансы. Конечно, можно присобачить костыли, но я надеялся, что есть подобный, но более развитой и работоспособный вариант.

Кроме того, желательно свести до минимума адаптацию структуры сериализуемого класса под конкретный сериализатор . Например, Json.NET для определения свойств, которые не нужно сериализовать, предлагает создавать спецметоды, имена которые зависят от имен соотв. свойств (см. документацию). Получается, что класс с данными затачивается под конкретное хранилище данных, а это плохо.
JohnSparrow
Дата: 28.05.2014 18:41:45
про Json.NET правильная ссылка: http://james.newtonking.com/json/help/html/ConditionalProperties.htm
Где-то в степи
Дата: 28.05.2014 18:44:49
JohnSparrow,
Вы как то странно себя мучаете. по дефолту Json формат не привязан ни к какому типу net.
ибо пользователем могут быть клиенты и на JS ,php, Java ..ets, равно и серилизатор vcf.
Но везде оставлены плющки net to net, в вашем случе надо было поглубже ознакомится с библиотекой
тынц или http://msdn.microsoft.com/ru-ru/library/system.web.script.serialization.javascriptserializer(v=vs.110).aspx
JohnSparrow
Дата: 28.05.2014 19:27:11
Где-то в степи,

Вас не затруднит уточнить первую ссылку (ака "тынц")?