2

Is it possible to access the keys of a mapping in the same order they appeared in the source document? I.e. if I have this simple document:

values:
    first: something1
    second: something2
    third: something3

Then I would be able to get a sequence of the keys in the original order: [first, second, third]?

Andre
  • 637
  • 6
  • 16

1 Answers1

5

One way of achieving this is to use the RepresentationModel API. It allows to get a representation of the YAML document that closely matches the underlying structure:

var stream = new YamlStream();
stream.Load(new StringReader(yaml));

var document = stream.Documents.First();

var rootMapping = (YamlMappingNode)document.RootNode;
var valuesMapping = (YamlMappingNode)rootMapping.Children[new YamlScalarNode("values")];

foreach(var tuple in valuesMapping.Children)
{
    Console.WriteLine("{0} => {1}", tuple.Key, tuple.Value);
}

The downside of this approach is that you need to parse the document "manually". Another approach is to use serialization, and use a type that preserves ordering. I am not aware of any ready-to-use implementation of IDictionary<TKey, TValue> that has this characteristic, but if you are not concerned about high performance, it is fairly simple to implement:

// NB: This is a minimal implementation that is intended for demonstration purposes.
//     Most of the methods are not implemented, and the ones that are are not efficient.
public class OrderPreservingDictionary<TKey, TValue>
    : List<KeyValuePair<TKey, TValue>>, IDictionary<TKey, TValue>
{
    public void Add(TKey key, TValue value)
    {
        Add(new KeyValuePair<TKey, TValue>(key, value));
    }

    public bool ContainsKey(TKey key)
    {
        throw new NotImplementedException();
    }

    public ICollection<TKey> Keys
    {
        get { throw new NotImplementedException(); }
    }

    public bool Remove(TKey key)
    {
        throw new NotImplementedException();
    }

    public bool TryGetValue(TKey key, out TValue value)
    {
        throw new NotImplementedException();
    }

    public ICollection<TValue> Values
    {
        get { throw new NotImplementedException(); }
    }

    public TValue this[TKey key]
    {
        get
        {
            return this.First(e => e.Key.Equals(key)).Value;
        }
        set
        {
            Add(key, value);
        }
    }
}

Once you have such container, you can take advantage of the Serialization API to parse the document:

var deserializer = new Deserializer();
var result = deserializer.Deserialize<Dictionary<string, OrderPreservingDictionary<string, string>>>(new StringReader(yaml));

foreach(var tuple in result["values"])
{
    Console.WriteLine("{0} => {1}", tuple.Key, tuple.Value);
}

You can see a fully working example in this fiddle

Antoine Aubry
  • 12,203
  • 10
  • 45
  • 74
  • Just for people looking for this with intention to implement ordered dict: I think this[TKey key] is implemented wrongly, you should first check if key exists unless you want duplicates. See also [this](https://msdn.microsoft.com/pl-pl/library/system.collections.specialized.ordereddictionary(v=vs.110).aspx) – MaLiN2223 May 05 '17 at 14:39
  • I believe the RepresentationModel example here doesn't guarantee the keys are read sequentially because, at least in the Yaml.NET code I've seen, the Children member is already a Dictionary at this point. Dictionary doesn't make any guarantees about iterating keys in the order they were inserted. (Side note: in cases like this, it happens to return them in the order they were inserted as of today but the docs say the order is undefined and we should not rely on it). I found I could implement an IYamlTypeConverter for my type and use IParser to read the map manually. Not ideal but it worked. – Amber Scouras Jun 14 '19 at 23:05
  • @AmberScouras you are correct. The RepresentationModel uses a standard `Dictionary<,>`, which will not preserve the order. I have opened an [issue](https://github.com/aaubry/YamlDotNet/issues/414) to remember to fix that. Thanks for pointing it out. – Antoine Aubry Jun 17 '19 at 08:51