1 year ago

#369224

test-img

andb70

Deserialize YAML to default values when top level node is optional

I use YamlDotNet.Serialization in my application and I plan to introduce a new node in my config.yaml but I want my app to be compatible with current version of config.

I found here a suggestion to manage optional mappings but it doensn't fit my scenario where the entire info node will be missing when the old yaml is used. In this case if I try to access the config.Informations.Version I get the exception Object reference not set to an instance of an object, because the config.Informations object hasn't been created.

Is there a way to obtain a valid config.Informations with default values?

old yaml:

sections:
  db:
    ...
  names:
    ...

new yaml:

info:
  lastEdit: 2022-04-04
  ...
  
sections:
  db:
    ...
  names:
    ...

Config.cs:

using System.Collections.Generic;
using System.ComponentModel;
using YamlDotNet.Serialization;

namespace myApp
{
    public class Config
    {
        [YamlMember(Alias = "info", ApplyNamingConventions = false)]
        [DefaultValue()]   // <<<<<<<<<< what should I use here?
        public DocumentInformations Informations { get; set; }

        [YamlMember(Alias = "sections", ApplyNamingConventions = false)]
        public Sections Sections { get; set; }
    }

    public class DocumentInformations
    {
        [YamlMember(Alias = "lastEdit", ApplyNamingConventions = false)]
        [DefaultValue("?")]
        public string LastEdit { get; set; }

        [YamlMember(Alias = "lastEditor", ApplyNamingConventions = false)]
        [DefaultValue("?")]
        public string LastEditor { get; set; }

        [YamlMember(Alias = "version", ApplyNamingConventions = false)]
        [DefaultValue("?")]
        public string Version { get; set; }

        [YamlMember(Alias = "notes", ApplyNamingConventions = false)]
        [DefaultValue("")]
        public string Notes { get; set; }
    }

    public class Sections
    {
        [YamlMember(Alias = "db", ApplyNamingConventions = false)]
        public Dictionary<string, Dictionary<string, string>> DB { get; set; }

        [YamlMember(Alias = "names", ApplyNamingConventions = false)]
        public Dictionary<string, Dictionary<string, string>> Names { get; set; }
    }
}

program.cs:

string filepath = $"{path}\\{project}\\config.yaml";
using StreamReader reader = File.OpenText(filepath);
var deserializer = new DeserializerBuilder()
    .IgnoreUnmatchedProperties()
    .Build();
var config = deserializer.Deserialize<Config>(reader);
Console.WriteLine($"Using Config version {config.Informations.Version}");

Found a solution

Having found that the DefaultValue attribute is not useful to initialize a property, I have slightly modified my code which now works, but I would like to know if there is a better way to do it, without putting code in the ctor:

public class Config
{
    public Config() => Informations = new DocumentInformations();

    [YamlMember(Alias = "info", ApplyNamingConventions = false)]
    public DocumentInformations Informations { get; set; }
    
    ...
}

public class DocumentInformations
{
    [YamlMember(Alias = "lastEdit", ApplyNamingConventions = false)]
    public string LastEdit { get; set; } = "<N/D>";

    [YamlMember(Alias = "lastEditor", ApplyNamingConventions = false)]
    public string LastEditor { get; set; } = "<N/D>";

    [YamlMember(Alias = "version", ApplyNamingConventions = false)]
    public string Version { get; set; } = "<N/D>";

    [YamlMember(Alias = "notes", ApplyNamingConventions = false)]
    public string Notes { get; set; } = "";
}

c#

yaml

default

toplevel

0 Answers

Your Answer

Accepted video resources