Convert List to Objects with custom names in YamlDotNet

35 views Asked by At

I'm trying to take a list of objects and serialize them as an objects instead of a list, the object name would be from a property on the object.

The issue I have is that I can get it to work, but I'm have to walk all the properties on the object manually.

I'm wondering if there is some built-in way to do that?

So I have

public class Config
{
   public List<Component> Components { get; set; }
}

public class Component
{
   public string Name { get; set; }
   public string OtherProperties { get; set; }
}

Then I want the output of this to look like

Components:
  nameFromObjectProperty:
    OtherProperties: ""
  nameOfOtherComponent:
    OtherProperties: ""
1

There are 1 answers

2
Gennadii Saltyshchak On

As an option, you can implement custom converter:

public sealed class ComponentConverter : IYamlTypeConverter
{
    private readonly IValueSerializer _valueSerializer =
        new SerializerBuilder().WithNamingConvention(CamelCaseNamingConvention.Instance).BuildValueSerializer();

    private readonly IValueDeserializer _valueDeserializer =
        new DeserializerBuilder().WithNamingConvention(CamelCaseNamingConvention.Instance).BuildValueDeserializer();

    public bool Accepts(Type type) => type == typeof(Component);

    public object ReadYaml(IParser parser, Type type)
    {
        parser.Consume<MappingStart>();

        var name = (string)_valueDeserializer
            .DeserializeValue(parser, typeof(string), new SerializerState(), _valueDeserializer);
        var otherPropertiesMap = (Dictionary<string, string>)_valueDeserializer
            .DeserializeValue(parser, typeof(Dictionary<string, string>), new SerializerState(), _valueDeserializer);
        var result = new Component
        {
            Name = name,
        };
        foreach (var (key, value) in otherPropertiesMap)
        {
            typeof(Component).GetProperty(key).SetValue(result, value);
        }
        parser.Consume<MappingEnd>();
        return result;
    }

    public void WriteYaml(IEmitter emitter, object value, Type type)
    {
        emitter.Emit(new MappingStart());
        var component = (Component)value;
        var otherPropertisMap = typeof(Component)
            .GetProperties()
            .Where(p => p.Name != nameof(Component.Name))
            .ToDictionary(p => p.Name, p => p.GetValue(component));
        _valueSerializer.SerializeValue(emitter, component.Name, typeof(string));
        _valueSerializer.SerializeValue(emitter, otherPropertisMap, typeof(Dictionary<string, string>));
        emitter.Emit(new MappingEnd());
    }
}

Usage:

var components = new[] {
    new Component()
    {
        Name = "a",
        OtherProperties = "b",
    },
    new Component()
    {
        Name = "c",
        OtherProperties = "d",
    },
};

var serializer = new SerializerBuilder()
    .WithNamingConvention(CamelCaseNamingConvention.Instance)
    .WithTypeConverter(new ComponentConverter())
    .Build();
    
var yaml = serializer.Serialize(components);
Console.WriteLine(yaml);

var componentConverter = new ComponentConverter();
var deserializer = new DeserializerBuilder()
    .WithNamingConvention(CamelCaseNamingConvention.Instance)
    .WithTypeConverter(new ComponentConverter())
    .Build();

var deserializedComponents = deserializer.Deserialize<Component[]>(yaml);
Console.WriteLine(deserializedComponents);

Example in .NET Fiddle