Search code examples
c#.netyamlyamldotnet

YamlDotNet doesn't support deserializing KeyValuePair<,> types


I've got a scenario in which I need to deserialize a sequence of maps to IEnumerable<KeyValuePair<string, string>> that wouldn't be solved by simply using Dictionary<string, string> instead. Since, apparently, YamlDotNet doesn't support KeyValuePair<,> out of the box, what alternative solutions could I rely on?

Example YAML:

- key1: value1
- key2: value2
- key3: value3

Solution

  • The issue here states that KeyValuePair<,> isn't, nor is it going to be, supported, according to aaubry, the owner of YamlDotNet. Yet, he does further state, in his response under the same issue, that it can be easily implemented by adding a custom INodeDeserializer. Since the example he included isn't applicable, however, I'll provide a more accurate implementation here.

    using System;
    using System.Collections.Generic;
    
    using YamlDotNet.Core;
    using YamlDotNet.Core.Events;
    using YamlDotNet.Serialization;
    
    
    internal class KeyValuePairNodeDeserializer : INodeDeserializer
    {
        public bool Deserialize(IParser parser, Type expectedType, Func<IParser, Type, object> nestedObjectDeserializer, out object value)
        {
            if (expectedType.IsGenericType && expectedType.GetGenericTypeDefinition() == typeof(KeyValuePair<,>))
            {
                parser.Consume<MappingStart>();
    
                var pairArgs = expectedType.GetGenericArguments();
    
                object key = null;
                object val = null;
    
                if (parser.Accept<Scalar>(out _))
                    key = nestedObjectDeserializer(parser, pairArgs[0]);
    
                if (parser.Accept<Scalar>(out _))
                    val = nestedObjectDeserializer(parser, pairArgs[1]);
    
                value = Activator.CreateInstance(expectedType, key, val);
    
                parser.Consume<MappingEnd>();
                return true;
            }
    
            value = null;
            return false;
        }
    }
    

    Now you can register your custom INodeDeserializer like below:

    var deserializer = new DeserializerBuilder()
        .WithNodeDeserializer(new KeyValuePairNodeDeserializer())     // Register here.
        .WithNamingConvention(UnderscoredNamingConvention.Instance)
        .Build();