aaubry / YamlDotNet

YamlDotNet is a .NET library for YAML

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

How to serialize the !include Directive in YamlDotNet

martindybal opened this issue · comments

Description:

I have a YAML configuration that includes external files using the !include directive, like this:

views:
  - !include views/home.yaml
  - title: Světnice
    path: dashboard
    badges: ...

I'm using YamlDotNet for YAML serialization in my C# project, and I'm wondering how to properly serialize this YAML structure while ensuring that the !include directive is correctly processed to include external files.

I'm tried

            views = new[]
            {
                new YamlScalarNode("!include views/home.yaml"),
                new
                {
                    title = dashboard.Title,
                    path = dashboard.UrlSlug,
                    badges = dashboard.GetBadges().Select(badge => badge.ToYml()),
                    cards = dashboard.GetCards().Select(card => card.ToYml())
                }.ToYml(),
            }

also

            views = new object[]
            {
                "!include views/home.yaml",
                new
                {
                    title = dashboard.Title,
                    path = dashboard.UrlSlug,
                    badges = dashboard.GetBadges().Select(badge => badge.ToYml()),
                    cards = dashboard.GetCards().Select(card => card.ToYml())
                },
            }

The result is both cases the same:

views:
- '!include views/home.yaml'  #this should not be a string
- title: Světla
  path: lovelace-lights
  badges:  ...

Is there something like RawYamlNode ?

On the YamlScalarNode try setting the scalar type to plain. That should do it.

@EdwardCooke thanks for the answer. The NodeType property is get only.

public override YamlNodeType NodeType

The property is actually style. Sorry about that.

public ScalarStyle Style { get; set; }

@EdwardCooke the result is same.

new YamlScalarNode("!include views/home.yaml")
{
    Style = ScalarStyle.Plain
}

- '!include views/home.yaml'

I hope this is only temporary solution :)

var yamlNode = YamlHelper.Include("views/home.yaml");
var serializer = new SerializerBuilder().Build();
var yaml = serializer.Serialize(yamlNode);
System.Console.WriteLine(yaml);

public static class YamlHelper
{
    public static YamlNode Include(string fileName)
    {
        return ToYaml($"!include {fileName}");
    }
        
    public static YamlNode ToYaml(string yaml)
    {
        var yamlStream = new YamlStream();
        yamlStream.Load(new StringReader(yaml));
        return yamlStream.Documents[0].RootNode;
    }
}

!include views/home.yaml

What if you set the tag property to !include or include? Then set the value to your file.

Just got to spend more time on this. This is much cleaner, using a yaml type convert and an include object.

using YamlDotNet.Core;
using YamlDotNet.Core.Events;
using YamlDotNet.Serialization;
using YamlDotNet.Serialization.NamingConventions;

var serializer = new SerializerBuilder()
    .WithTagMapping("!include", typeof(IncludedObject))
    .WithTypeConverter(new TestTypeConverter())
    .WithNamingConvention(CamelCaseNamingConvention.Instance)
    .Build();


var o = new object[] {
    new IncludedObject{ Filename = "test.yaml" },
    new Book
    {
        Title = "a title",
        Path = "a path",
        Badges = "some badges"
    }
};
var serialized = serializer.Serialize(o);
Console.WriteLine(serialized);

class Outer
{
    public object[] Items { get; set; }
}
class Book
{
    public string Title { get; set; }
    public string Path { get; set; }
    public string Badges { get; set; }
}

// This is the magic that makes it easily re-usable
class TestTypeConverter : IYamlTypeConverter
{
    public bool Accepts(Type type) => type == typeof(IncludedObject);

    public object? ReadYaml(IParser parser, Type type)
    {
        throw new NotImplementedException();
    }

    public void WriteYaml(IEmitter emitter, object? value, Type type)
    {
        if (type != typeof(IncludedObject))
        {
            return;
        }
        if (value is IncludedObject o)
        {
            emitter.Emit(new Scalar(null, "!include", o.Filename, ScalarStyle.Plain, false, false));
        }
    }
}

class IncludedObject
{
    public string Filename { get; set; } = string.Empty;
}

Results in

- !include test.yaml
- title: a title
  path: a path
  badges: some badges

If you want to see how to read in an include directive, checkout my answer here.

#909