如何反序列化不可变数据结构

本文关键字:数据结构 不可变 反序列化 | 更新日期: 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();

你最终不得不为你的整个模型创建一个并行的构建器集,然而,例如,如果MyDataMyOtherData类型的第三个参数(我已经改变了这个例子,使用记录而不是类,使它简洁):

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;