Plain text serialization

Я люблю простые костыли.

Когда требуется сериализовать некоторые поля в какой-то текстовый формат,
бывает удобно использовать промежуточное представление данных вида:

// Name/Value item
public class NVItem {
  public string? Name;
  public object? Value;
  public IEnumerable<NVItem>? SubItems;

  public NVItem(string? name, object? value) {
    Name = name;
    if (value==null) return;
    SubItems = value as IEnumerable<NVItem>;
    if (SubItems != null) return;
    
    // Упс!
    Value = value as string;
    if (Value != null) return;
    
    var num = value as IEnumerable;
    if (num==null) {
      Value = value;
    } else {
      SubItems = num.Cast<object>().Select(t => new NVItem(null, t));
    }
  }
}

Которое затем конвертируется в XML, JSON или что-нибудь ещё.

Например, так:

IEnumerable<NVItem> GetTypeInfo(Type type) {
  return new NVItem[] {
    new NVItem("Name", type.Name),
    new NVItem("FullName", type.FullName),
    new NVItem("CustomAttributes", type.CustomAttributes),
  };
}

Это можно реализовать и по-изящнее, но костыль улучшать - только портить.

Если требуется преобразовать данное представление в текст, воспользуемся следующими методами:

public static class NVItemExtensions {
  
  public static IEnumerable<string> ToStrings(this NVItem item) {
    yield return item.Name+'='+item.Value?.ToString();
    if (item.SubItems!=null) {
      foreach (string s in item.SubItems.SelectMany(t => t.ToStrings())) {
        yield return "    "+s;
      }
    }
  }
  
  public static string AsString(this IEnumerable<NVItem> items) { 
    return string.Join('\n', items.SelectMany(t => t.ToStrings()));
  }
}

string info = GetTypeInfo(typeof(string)).AsString();

Получаем:

Name=String
FullName=System.String
CustomAttributes=
    =[System.SerializableAttribute()]
    =[System.Runtime.CompilerServices.NullableContextAttribute((Byte)1)]
    =[System.Runtime.CompilerServices.NullableAttribute((Byte)0)]
    =[System.Reflection.DefaultMemberAttribute("Chars")]
    =[System.Runtime.Versioning.NonVersionableAttribute()]
    ...

Теперь заметим, что если:
- наши Name не начинаются с пробела и не содержат символов '=' и '\n',
- наши Value не содержат символов '\n',
- и нам не надо различать пустые строки и null,
то получаемый текст можно преобразовать обратно, за тем исключением, что Value теперь строки.

У меня не было такой надобности, но если сам пишешь - сам читаешь, то это вполне рабочий вариант сериализации. А вот сохранять настройки в подобный файл с намерением редактировать вручную, это не лучший выбор.

Форматы конфигов, как и все другие решения для всего, придумывают злые гении с подобающими им амбициями:
Habr: Tree — убийца JSON, XML, YAML и иже с ними
Habr: Почему JSON и YAML мешают вам писать нормальные конфиги (и чем их заменить)
Habr: JSON? JSONB? BSON? CBOR? MsgPack? А, VaryPackǃ


@AmirYantimirov
20.01.2026 05:54 UTC
Первоисточник

Комментарии

@yarosroman
20.01.2026 05:02 UTC
+5

Вы перепутали habr и govnokod.ru.

@AmirYantimirov
20.01.2026 09:02 UTC
-2

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

20.01.2026 14:22 UTC
+1

if (true.ToString().ToLower() == "true") {...} тоже делает, то, что автор хочет. Вопрос - "зачем"?

Я честно попытался прикинуть, в каком случае может понадобится ваш костыль - но ничего так и не придумалось. Можете дать пример, когда ваш костыль даёт хоть какую-то пользу перед другими сериализациями?

@yarosroman
20.01.2026 10:32 UTC
0

Ну для key\value существует Dictionary, который сереализуется в JSON и XML без всяких костылей, а если нужны Subitems, то легко положить в словарь и IEnumerable. Костыли они и на то костыли, что ими не хвастаются.

@AmirYantimirov
20.01.2026 23:35 UTC
0

Как минимум, Dictionary не сохраняет порядок добавления.

20.01.2026 23:40 UTC
0

OrderedDictionary

20.01.2026 23:49 UTC
-1

Да.

А как насчет вложенности?

20.01.2026 23:52 UTC
0

Можно спокойно добавить любую коллекцию в словарь.

21.01.2026 01:23 UTC
-2

Вложенность больше двух уровней.

21.01.2026 05:08 UTC
+1

Напишите все вводные сразу, а не по одному сообщению

Кто мешает делать вложенность больше двух уровней, кстати?

21.01.2026 09:42 UTC
0

OrderedDictionary<Key, object?>

Но как уже выше сказали - нужны все вводные.

21.01.2026 10:04 UTC
0

Вообще-то, я всегда сериализовал в XML вручную, и когда сейчас глянул в документацию, понял, что не представляю возможностей штатной сериализации стандартных контейнеров.

Могли бы вы представить этот пример через Dictionary, и каков будет результат сериализации в XML?

IEnumerable GetTypeInfo(Type type) {
return new NVItem[] {
new NVItem("Name", type.Name),
new NVItem("FullName", type.FullName),
new NVItem("CustomAttributes", type.CustomAttributes),
};
}

23.01.2026 22:06 UTC
0

Опять не с того конца.

Какой результат хочется получить?

Нужно конкретный набор объектов сериализовать в xml без какой-то предопределённой структуры, чтобы потом по этому xml воссоздать исходные объекты?

Или же нужно какую-то предопределённую структуру в xml выразить в коде?

https://learn.microsoft.com/en-us/dotnet/standard/serialization/xml-and-soap-serialization

Стандартными средствами можно обоими путями пойти.

Если хочется просто сериализовать и не важно во что, то все стандартные коллекции вполне поддаются сериализации в XML.

Classes that implement ICollection or IEnumerable.

Если хочется что-то конкретное, то гибче всего - руками построить XmlDocument с нужной структурой.

Справедливо и в обратную сторону.

23.01.2026 23:46 UTC
-1

Да. Сериализовать вручную - через XmlDocument.

По штатной сериализацией я имел в ввиду что-то вроде:
var serializer = new XmlSerializer(typeof(NVItem));
но это требует слишком много усилий, по сравнению с ручным методом.

Кроме того, если требуется сериализовать лишь некоторые поля, нам все равно придется использовать некоторый промежуточный носитель.

Я хорошо представляю, как можно вместо NVItem использовать комбинацию Dictionary и List, и затем сериализовать это через XmlDocument.

Мой вопрос в том - можно ли такую комбинацию сходу подставить в XmlSerilizer?

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

@nin-jin
22.01.2026 21:50 UTC
-3

Актуальная статья про Tree всё же эта: https://habr.com/ru/articles/503240/