Как десериализовать неизменную структуру данных?
Как бы я десериализовал YAML в неизменную структуру данных?
например, у меня есть этот YAML:
Value: SomeString
Number: 99
И эта структура данных:
public class MyData
{
public MyData(string value, int number)
{
Value = value;
Number = number;
}
public string Value { get; }
public int Number { get; }
}
Для этого я бы использовал конструктор. Так что как-то мне нужно сначала получить Dictionary<string, object>
проанализировал YAML, относящийся к моему классу (так что 99 будет int, а не string), затем просканировал мой тип на предмет соответствующего конструктора,
3 ответа
Хотя в вопросе об этом не упоминается, я предполагаю, что вы используете YamlDotNet (или SharpYaml, который является вилкой YamlDotNet)
YamlDotNet не поддерживает десериализацию в классы, у которых нет конструктора по умолчанию, но один из вариантов достижения того, что вы хотите, - десериализация в промежуточный тип Builder, который является изменяемым, который может создать окончательный тип. например
public class MyDataBuilder
{
public string Value { get; set; }
public int Number { get; set; }
public MyData Build() => new MyData(Value, Number);
}
А затем используйте что-то вроде:
deserializer.Deserialize<MyDataBuilder>(yaml).Build();
Однако вам придется создать параллельный набор построителей для всей модели, например, если
MyData
имел третий параметр типа
MyOtherData
(Я изменил пример, чтобы использовать записи вместо классов, чтобы сделать его кратким):
public record MyOtherData(string OtherValue);
public record MyData(string Value, int Number, MyOtherData otherData);
В этом случае нам понадобится еще один Строитель:
public class MyOtherDataBuilder
{
public string OtherValue { get; set; }
}
А также
MyDataBuilder
будет выглядеть так:
public class MyDataBuilder
{
public string Value { get; set; }
public int Number { get; set; }
public MyOtherDataBuilder MyOtherData { get; set; }
public MyData Build() => new MyData(Value, Number, MyOtherData.Build());
}
Это старый, но удивительно актуальный вопрос. Теперь, с записями в C#, неизменяемыми коллекциями в .net, отсутствие возможности десериализации неизменяемых данных является препятствием — нам не нужно менять все наши типы данных только для того, чтобы иметь возможность десериализовать. Один практический обходной путь, который я нашел, - это сначала преобразовать yaml в json, а затем работать с json предпочтительным способом - System.Text.Json, Newtonsoft и т. д.
Вот как это сделать проще всего:
static string ConvertToJson(string yaml) {
object DeserializeYaml() =>
new DeserializerBuilder()
.Build()
.Deserialize(new StringReader(yaml))
?? throw new InvalidOperationException("Cannot deserialize yaml string:" + Environment.NewLine + yaml);
string SerializeYamlObjectToJson(object yamlObject) =>
new SerializerBuilder()
.JsonCompatible()
.Build()
.Serialize(yamlObject);
return SerializeYamlObjectToJson(DeserializeYaml());
}
Единственным недостатком, потенциально большим, является производительность. Однако я чувствую, что это редко является важным требованием для yaml.
Используйте API-интерфейс FormatterServices.GetUninitializedObject (это вообще НЕ вызовет конструкторов), а затем используйте отражение для установки полей.
Пример кода:
var instance = FormatterServices.GetUninitializedObject(typeof(MyData));
var flags = BindingFlags.NonPublic | BindingFlags.Instance;
var type = typeof(MyData);
var stringField = type.GetField("_value", flags);
stringField.SetValue(instance, "SomeString");
var numberField = type.GetField("_number", flags);
numberField.SetValue(instance, 99);
MyData data = (MyData)instance;