如何反序列化不可变数据结构
本文关键字:数据结构 不可变 反序列化 | 更新日期: 2023-09-27 18:06:51
如何将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; }
}
对于这个,我将使用构造函数。因此,不知怎么的,我需要首先检索从YAML解析的Dictionary<string, object>
关于我的类(所以99将是int,而不是字符串),然后扫描我的类型,寻找合适的构造函数,
虽然问题没有提到它,我假设你正在使用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);
在这种情况下,我们需要另一个Builder:
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,然后以您喜欢的方式(System.Text)处理json。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的重要需求。
使用FormatterServices。GetUninitializedObject API(这根本不会调用任何构造函数),然后使用反射来设置字段。
代码示例:
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;