aaubry / YamlDotNet

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

Overriding the name of a type dynamically #914

Closed martinjt closed 1 month ago

martinjt commented 3 months ago

I'm playing around with building a class that will model how the OpenTelemetry Collector's config works.

Specifically, they have a format that takes a list of components, but does them as Objects

receivers:
   otlp/1:
     <stuff>
   otlp/2:
    <stuff>

What I want to be able to do is represent that as a list in code (since the OTLP and OTLP/2 objects are the same thing.

So what I want to do is "just" override the name part, and the rendering of the List as a objects.

The classes will look something like this:

public class Config
{
   public List<Receivers> Receivers { get; } = [];
}

public abstract class Receiver
{
   public abstract string ReceiverType;
   public string ReceiverName;
}

Is this possible, I've been struggling to make it work.

EdwardCooke commented 1 month ago

Yes, you can do this using the IYamlConvertible interface,

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

var data = new Config();
data.Receivers.Add(new Receiver());
data.Receivers.Add(new Receiver());
data.Receivers.Add(new Receiver());

var serializer = new SerializerBuilder().Build();
var yaml = serializer.Serialize(data);
Console.WriteLine(yaml);

public class Config : IYamlConvertible
{
    public List<Receiver> Receivers { get; } = [];

    public void Read(IParser parser, Type expectedType, ObjectDeserializer nestedObjectDeserializer)
    {
    }

    public void Write(IEmitter emitter, ObjectSerializer nestedObjectSerializer)
    {
        emitter.Emit(new YamlDotNet.Core.Events.MappingStart());
        emitter.Emit(new Scalar("Receivers"));
        emitter.Emit(new YamlDotNet.Core.Events.MappingStart());

        for (var ordinal = 0; ordinal < Receivers.Count; ordinal++)
        {
            emitter.Emit(new Scalar($"otlp/{ordinal + 1}"));
            nestedObjectSerializer(Receivers[ordinal], typeof(Receiver));
        }

        emitter.Emit(new YamlDotNet.Core.Events.MappingEnd());
        emitter.Emit(new YamlDotNet.Core.Events.MappingEnd());
    }
}

public class Receiver
{
    public string ReceiverType = Guid.NewGuid().ToString();
    public string ReceiverName = Guid.NewGuid().ToString();
}

Results in

Receivers:
  otlp/1:
    ReceiverType: ca5a6d4f-7faa-4420-8257-e97a5bdbda00
    ReceiverName: 900e3564-2859-4277-9f29-674b16f597ed
  otlp/2:
    ReceiverType: ce04b24a-96f3-4a8c-a331-43474a3fb6ea
    ReceiverName: a13e3668-f6b3-49ba-a2a5-9947182e08f9
  otlp/3:
    ReceiverType: 55e810bb-6e44-43c5-90db-6e25e7f6258e
    ReceiverName: fdf65d79-906f-4927-be96-37701a47177d
EdwardCooke commented 1 month ago

Since I gave a working example and it's been a few weeks I'm going to close this.