aaubry / YamlDotNet

YamlDotNet is a .NET library for YAML
MIT License
2.48k stars 466 forks source link

How to serialize the !include Directive in YamlDotNet #880

Closed martindybal closed 2 days ago

martindybal commented 6 months ago

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 ?

EdwardCooke commented 6 months ago

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

martindybal commented 6 months ago

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

https://github.com/aaubry/YamlDotNet/blob/847230593e95750d4294ca72c98a4bd46bdcf265/YamlDotNet/RepresentationModel/YamlScalarNode.cs#L219

EdwardCooke commented 6 months ago

The property is actually style. Sorry about that.

https://github.com/aaubry/YamlDotNet/blob/847230593e95750d4294ca72c98a4bd46bdcf265/YamlDotNet/RepresentationModel/YamlScalarNode.cs#L68

martindybal commented 6 months ago

@EdwardCooke the result is same.

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

- '!include views/home.yaml'

martindybal commented 6 months ago

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

EdwardCooke commented 6 months ago

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

EdwardCooke commented 4 months ago

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
EdwardCooke commented 4 months ago

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

https://github.com/aaubry/YamlDotNet/issues/909

martindybal commented 2 days ago

@EdwardCooke thanks!