Как десериализовать неизменную структуру данных?

Как бы я десериализовал 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;
Другие вопросы по тегам